自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

三個(gè)UI框架幫你用Python編寫用戶友好的應(yīng)用程序

譯文
開發(fā) 架構(gòu)
了解如何選擇合適的圖形用戶界面庫來編寫用戶友好的應(yīng)用程序。

譯者 | 布加迪

審校 | 孫淑娟 梁策

Python有許多圖形用戶界面(GUI)框架可供使用。其中大多數(shù)非常成熟,得到了開源和商業(yè)支持;另一些主要綁定到可用的C/C++ UI庫。無論如何,在使用庫的選擇上,可考慮三個(gè)因素:

  • 成熟度:它是否穩(wěn)定且受到社區(qū)的大力支持,是否文檔完備?
  • 與Python集成:可能聽上去無關(guān)緊要,但它可能對工具包構(gòu)成了很高的準(zhǔn)入門檻(你不想覺得好像是在用匯編程序編寫GUI;畢竟,它是Python)。
  • 它是否支持你的用例?如果你主要想編寫表單,那么Pyforms或Tkinter之類的庫可能更適合。(Tkinker家喻戶曉。)如果你的GUI較復(fù)雜,那么wxPython可能更好,因?yàn)樗С值墓δ芎軓V泛。

優(yōu)秀的系統(tǒng)管理員應(yīng)該知道如何創(chuàng)建用戶友好的應(yīng)用程序。它們在大幅提高你和用戶的工作效率上會讓你大吃一驚。

有很多框架可供選擇。本文將概述其中的三個(gè)框架:Rich、Tkinter和DearPyGui。

準(zhǔn)備好環(huán)境

如果想學(xué)習(xí)以下簡短教程,請運(yùn)行以下命令,準(zhǔn)備好環(huán)境:

$ git clone https://github.com/josevnz/rpm_query
$ cd rpm_query
$ python3 -m venv --system-site-packages ~/virtualenv/rpm_query
$ . ~/virtualenv/rpm_query/bin/activate
$ python3 setup.py build
$ cp reporter build/scripts-3.?

準(zhǔn)備完畢,現(xiàn)在開始。

顯示按大小排序的RPM列表

該示例應(yīng)用程序不是很復(fù)雜。它應(yīng)該清晰地顯示以下輸出:

$ ./rpmq_simple.py --limit 10
linux-firmware-20210818: 395,099,476
code-1.61.2: 303,882,220
brave-browser-1.31.87: 293,857,731
libreoffice-core-7.0.6.2: 287,370,064
thunderbird-91.1.0: 271,239,962
firefox-92.0: 266,349,777
glibc-all-langpacks-2.32: 227,552,812
mysql-workbench-community-8.0.23: 190,641,403
java-11-openjdk-headless-11.0.13.0.8: 179,469,639
iwl7260-firmware-25.30.13.0: 148,167,043

它應(yīng)該還可讓用戶重新運(yùn)行查詢,同時(shí)覆蓋匹配數(shù)量和包名稱,以及按大小(字節(jié))來排序。

現(xiàn)在一切準(zhǔn)備就緒,你可以開始創(chuàng)建應(yīng)用程序了。以下三個(gè)框架可供參考。

1. Rich

Rich 是威爾·麥克古根 (Will McGugan) 編寫的一款極易使用的框架。它不提供大量widget小組件(一個(gè)仍在測試階段,名為Textual的姐妹項(xiàng)目更注重組件。)

安裝Rich

安裝Rich框架:

$ pip install rich

這是我的Python腳本代碼。它在清晰的表上生成進(jìn)度條和結(jié)果:

#!/usr/bin/env python
"""
# rpmq_rich.py - A simple CLI to query the sizes of RPM on your system
Author: Jose Vicente Nunez
"""
import argparse
import textwrap
from reporter import __is_valid_limit__
from reporter.rpm_query import QueryHelper
from rich.table import Table
from rich.progress import Progress
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=textwrap.dedent(__doc__))
parser.add_argument(
"--limit",
type=__is_valid_limit__, # Custom limit validator
action="store",
default=QueryHelper.MAX_NUMBER_OF_RESULTS,
help="By default results are unlimited but you can cap the results"
)
parser.add_argument(
"--name",
type=str,
action="store",
help="You can filter by a package name."
)
parser.add_argument(
"--sort",
action="store_false",
help="Sorted results are enabled bu default, but you fan turn it off"
)
args = parser.parse_args()
with QueryHelper(
name=args.name,
limit=args.limit,
sorted_val=args.sort
) as rpm_query:
rpm_table = Table(title="RPM package name and sizes")
rpm_table.add_column("Name", justify="right", style="cyan", no_wrap=True)
rpm_table.add_column("Size (bytes)", justify="right", style="green")
with Progress(transient=True) as progress:
querying_task = progress.add_task("[red]RPM query...", start=False)
current = 0
for package in rpm_query:
if current >= args.limit:
break
rpm_table.add_row(f"{package['name']}-{package['version']}", f"{package['size']:,.0f}")
progress.console.print(f"[yellow]Processed package: [green]{package['name']}-{package['version']}")
current += 1
progress.update(querying_task, advance=100.0)
progress.console.print(rpm_table)

為原始腳本添加表和進(jìn)度條非常容易。

下面是全新改進(jìn)后的文本UI的樣子。

圖1

2.Tkinter

Tkinter結(jié)合了多個(gè)框架:TCL、TK和widget小組件(Ttk)。

該框架相當(dāng)成熟,文檔和示例完備。建議先主要遵循??官方教程??,在掌握了基礎(chǔ)知識后,可以繼續(xù)閱讀感興趣的其他教程。

有幾點(diǎn)需要注意:

  • 檢查你的系統(tǒng)是否正確安裝了Tkinter,如下所示:python -m tkinter。
  • 使用回調(diào)函數(shù)(command=),使你的GUI響應(yīng)事件。
  • Tkinter使用特殊變量進(jìn)行通信,這些變量幫你跟蹤更改(Var,比如StringVar)。

Tkinter 中的代碼是什么樣的?

#!/usr/bin/env python
"""
# rpmq_tkinter.py - A simple CLI to query the sizes of RPM on your system
This example is more complex because:
* Uses callbacks (commands) to update the GUI and also deals
* Deals with the placement of components using a frame with Grid and a flow layout
Author: Jose Vicente Nunez
"""
import argparse
import textwrap
from tkinter import *
from tkinter.ttk import *
from reporter import __is_valid_limit__
from reporter.rpm_query import QueryHelper
def __initial__search__(*, window: Tk, name: str, limit: int, sort: bool, table: Treeview) -> NONE:
"""
Populate the table with an initial search using CLI args
:param window:
:param name:
:param limit:
:param sort:
:param table:
:return:
"""
with QueryHelper(name=name, limit=limit, sorted_val=sort) as rpm_query:
row_id = 0
for package in rpm_query:
if row_id >= limit:
break
package_name = f"{package['name']}-{package['version']}"
package_size = f"{package['size']:,.0f}"
table.insert(
parent='',
index='end',
iid=row_id,
text='',
values=(package_name, package_size)
)
window.update() # Update the UI as soon we get results
row_id += 1
def __create_table__(main_w: Tk) -> Treeview:
"""
* Create a table using a tree component, with scrolls on both sides (vertical, horizontal)
* Let the UI 'pack' or arrange the components, not using a grid here
* The table reacts to the actions and values of the components defined on the filtering components.
:param main_w
"""
scroll_y = Scrollbar(main_w)
scroll_y.pack(side=RIGHT, fill=Y)
scroll_x = Scrollbar(main_w, orient='horizontal')
scroll_x.pack(side=BOTTOM, fill=X)
tree = Treeview(main_w, yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
tree.pack()
scroll_y.config(command=tree.yview)
scroll_x.config(command=tree.xview)
tree['columns'] = ('package_name', 'package_size')
tree.column("#0", width=0, stretch=NO)
tree.column("package_name", anchor=CENTER, width=500)
tree.column("package_size", anchor=CENTER, width=100)
tree.heading("#0", text="", anchor=CENTER)
tree.heading("package_name", text="Name", anchor=CENTER)
tree.heading("package_size", text="Size (bytes)", anchor=CENTER)
return tree
def __cli_args__() -> argparse.Namespace:
"""
Command line argument parsing
:return:
"""
parser = argparse.ArgumentParser(description=textwrap.dedent(__doc__))
parser.add_argument(
"--limit",
type=__is_valid_limit__, # Custom limit validator
action="store",
default=QueryHelper.MAX_NUMBER_OF_RESULTS,
help="By default results are unlimited but you can cap the results"
)
parser.add_argument(
"--name",
type=str,
action="store",
default="",
help="You can filter by a package name."
)
parser.add_argument(
"--sort",
action="store_false",
help="Sorted results are enabled bu default, but you fan turn it off"
)
return parser.parse_args()
def __reset_command__() -> None:
"""
Callback to reset the UI form filters
Doesn't trigger a new search. This is on purpose!
:return:
"""
query_v.set(args.name)
limit_v.set(args.limit)
sort_v.set(args.sort)
def __ui_search__() -> None:
"""
Re-do a search using UI filter settings
:return:
"""
for i in results_tbl.get_children():
results_tbl.delete(i)
win.update()
__initial__search__(
window=win, name=query_v.get(), limit=limit_v.get(), sort=sort_v.get(), table=results_tbl)
def test(arg):
print(arg)
if __name__ == "__main__":
args = __cli_args__()
win = Tk()
win.title("RPM Search results")
# Search frame with filtering options. Force placement using a grid
search_f = LabelFrame(text='Search options:', labelanchor=N, relief=FLAT, padding=1)
query_v = StringVar(value=args.name)
query_e = Entry(search_f, textvariable=query_v, width=25)
limit_v = IntVar(value=args.limit)
limit_l = Label(search_f, text="Limit results: ")
query_l = Spinbox(
search_f,
from_=1, # from_ is not a typo and is annoying!
to=QueryHelper.MAX_NUMBER_OF_RESULTS,
textvariable=limit_v
)
sort_v = BooleanVar(value=args.sort)
sort_c = Checkbutton(search_f, text="Sort by size", variable=sort_v)
search_btn = Button(search_f, text="Search RPM", command=__ui_search__)
clear_btn = Button(search_f, text="Reset filters", command=__reset_command__)
package_l = Label(search_f, text="Package name: ").grid(row=0, column=0, sticky=W)
search_f.grid(column=0, row=0, columnspan=3, rowspan=4)
limit_l.grid(row=1, column=0, sticky=W)
query_e.grid(row=0, column=1, columnspan=2, sticky=W)
query_l.grid(row=1, column=1, columnspan=1, sticky=W)
sort_c.grid(row=2, column=0, columnspan=1, sticky=W)
search_btn.grid(row=3, column=0, columnspan=2, sticky=W)
clear_btn.grid(row=3, column=1, columnspan=1, sticky=W)
search_f.pack(side=TOP, fill=BOTH, expand=1)
results_tbl = __create_table__(win)
results_tbl.pack(side=BOTTOM, fill=BOTH, expand=1)
__initial__search__(
window=win, name=query_v.get(), limit=limit_v.get(), sort=sort_v.get(), table=results_tbl)
win.mainloop()

代碼比較冗長,主要是由于事件處理。

圖2

但是,這也意味著一旦腳本啟動,就可以重新運(yùn)行查詢,只需在搜索選項(xiàng)框上修改參數(shù)。

3. DearPyGui

喬納森·霍夫施塔特 (Jonathan Hoffstadt) 開發(fā)的DearPyGui可跨平臺(Linux、Windows和macOS),具備一些出色的功能。

安裝DearPyGui

如果你有當(dāng)前系統(tǒng)(比如Fedora 33或Windows 10 Pro),安裝起來應(yīng)該很容易:

$ pip install dearpygui

以下是用DearPyGui重寫的應(yīng)用程序:

#!/usr/bin/env python
"""
# rpmq_dearpygui.py - A simple CLI to query the sizes of RPM on your system
Author: Jose Vicente Nunez
"""
import argparse
import textwrap
from reporter import __is_valid_limit__
from reporter.rpm_query import QueryHelper
import dearpygui.dearpygui as dpg
TABLE_TAG = "query_table"
MAIN_WINDOW_TAG = "main_window"
def __cli_args__() -> argparse.Namespace:
"""
Command line argument parsing
:return:
"""
parser = argparse.ArgumentParser(description=textwrap.dedent(__doc__))
parser.add_argument(
"--limit",
type=__is_valid_limit__, # Custom limit validator
action="store",
default=QueryHelper.MAX_NUMBER_OF_RESULTS,
help="By default results are unlimited but you can cap the results"
)
parser.add_argument(
"--name",
type=str,
action="store",
default="",
help="You can filter by a package name."
)
parser.add_argument(
"--sort",
action="store_false",
help="Sorted results are enabled bu default, but you fan turn it off"
)
return parser.parse_args()
def __reset_form__():
dpg.set_value("package_name", args.name)
dpg.set_value("limit_text", args.limit)
dpg.set_value("sort_by_size", args.sort)
def __run_initial_query__(
*,
package: str,
limit: int,
sorted_elem: bool
) -> None:
"""
Need to ensure the table gets removed.
See issue: https://github.com/hoffstadt/DearPyGui/issues/1350
:return:
"""
if dpg.does_alias_exist(TABLE_TAG):
dpg.delete_item(TABLE_TAG, children_only=False)
if dpg.does_alias_exist(TABLE_TAG):
dpg.remove_alias(TABLE_TAG)
with dpg.table(header_row=True, resizable=True, tag=TABLE_TAG, parent=MAIN_WINDOW_TAG):
dpg.add_table_column(label="Name", parent=TABLE_TAG)
dpg.add_table_column(label="Size (bytes)", default_sort=True, parent=TABLE_TAG)
with QueryHelper(
name=package,
limit=limit,
sorted_val=sorted_elem
) as rpm_query:
current = 0
for package in rpm_query:
if current >= args.limit:
break
with dpg.table_row(parent=TABLE_TAG):
dpg.add_text(f"{package['name']}-{package['version']}")
dpg.add_text(f"{package['size']:,.0f}")
current += 1
def __run__query__() -> None:
__run_initial_query__(
package=dpg.get_value("package_name"),
limit=dpg.get_value("limit_text"),
sorted_elem=dpg.get_value("sort_by_size")
)
if __name__ == "__main__":
args = __cli_args__()
dpg.create_context()
with dpg.window(label="RPM Search results", tag=MAIN_WINDOW_TAG):
dpg.add_text("Run a new search")
dpg.add_input_text(label="Package name", tag="package_name", default_value=args.name)
with dpg.tooltip("package_name"):
dpg.add_text("Leave empty to search all packages")
dpg.add_checkbox(label="Sort by size", tag="sort_by_size", default_value=args.sort)
dpg.add_slider_int(
label="Limit",
default_value=args.limit,
tag="limit_text",
max_value=QueryHelper.MAX_NUMBER_OF_RESULTS
)
with dpg.tooltip("limit_text"):
dpg.add_text(f"Limit to {QueryHelper.MAX_NUMBER_OF_RESULTS} number of results")
with dpg.group(horizontal=True):
dpg.add_button(label="Search", tag="search", callback=__run__query__)
with dpg.tooltip("search"):
dpg.add_text("Click here to search RPM")
dpg.add_button(label="Reset", tag="reset", callback=__reset_form__)
with dpg.tooltip("reset"):
dpg.add_text("Reset search filters")
__run_initial_query__(
package=args.name,
limit=args.limit,
sorted_elem=args.sort
)
dpg.create_viewport(title='RPM Quick query tool')
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

請注意,DearPyGui在嵌套組件時(shí)使用上下文,因而創(chuàng)建GUI時(shí)容易得多。代碼也沒有Tkinter代碼那么冗長,對類型的支持也要更好(比如說,PyCharm提供了自動完成方法參數(shù)的功能)。

DearPyGui還很年輕(目前是版本1.0.3),也有一些bug,尤其是在舊的Linux發(fā)行版上。但它很有前景,正在積極開發(fā)中。

那么,DearPyGui中UI是什么樣子呢?

圖3

原文標(biāo)題:3 UI frameworks for writing user-friendly applications in Python,作者:Jose Vicente Nunez

責(zé)任編輯:華軒 來源: 51CTO
相關(guān)推薦

2021-09-14 09:39:06

設(shè)計(jì)系統(tǒng)框架設(shè)計(jì)原則

2018-06-22 09:00:00

Java框架Pronghorn

2022-02-28 16:05:53

開發(fā)RTOS數(shù)據(jù)

2015-07-07 09:06:32

云計(jì)算應(yīng)用部署云計(jì)算成本

2010-11-03 13:19:28

2020-10-10 10:30:31

JavaScript開發(fā)技術(shù)

2023-02-13 08:45:26

2018-12-03 08:25:24

2019-02-11 09:35:04

Python應(yīng)用程序Tornado

2009-07-14 18:10:38

Swing應(yīng)用程序框架

2012-03-15 15:35:51

iUI框架EclipseiOS Web

2011-04-01 11:01:02

應(yīng)用程序BlackBerryJava

2020-01-15 14:20:07

Node.js應(yīng)用程序javascript

2018-06-06 09:00:16

2023-12-21 16:25:23

WeChatSnapchatShopee

2012-05-29 10:04:08

2023-06-13 13:38:00

FlaskPython

2024-09-06 10:46:04

2024-01-02 00:18:56

Buffalo項(xiàng)目Go Web框架

2015-03-04 14:30:22

DIY平臺移動應(yīng)用
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號