IT科技类资讯

三个UI框架帮你用Python编写用户友好的应用程序

时间:2010-12-5 17:23:32  作者:域名   来源:域名  查看:  评论:0
内容摘要:译者 | 布加迪审校 | 孙淑娟 梁策Python有许多图形用户界面(GUI)框架可供使用。其中大多数非常成熟,得到了开源和商业支持;另一些主要绑定到可用的C/C++ UI库。无

译者 | 布加迪

审校 | 孙淑娟 梁策

Python有许多图形用户界面(GUI)框架可供使用。架帮其中大多数非常成熟,用P应用得到了开源和商业支持;另一些主要绑定到可用的写用C/C++ UI库。无论如何,户友好在使用库的程序选择上,可考虑三个因素:

成熟度:它是架帮否稳定且受到社区的大力支持,是用P应用否文档完备?与Python集成:可能听上去无关紧要,但它可能对工具包构成了很高的写用准入门槛(你不想觉得好像是在用汇编程序编写GUI;毕竟,它是户友好Python)。它是程序否支持你的用例?如果你主要想编写表单,那么Pyforms或Tkinter之类的架帮库可能更适合。(Tkinker家喻户晓。用P应用)如果你的写用GUI较复杂,那么wxPython可能更好,户友好因为它支持的程序功能很广泛。

优秀的系统管理员应该知道如何创建用户友好的应用程序。它们在大幅提高你和用户的工作效率上会让你大吃一惊。

有很多框架可供选择。云南idc服务商本文将概述其中的三个框架:Rich、Tkinter和DearPyGui。

准备好环境

如果想学习以下简短教程,请运行以下命令,准备好环境:

$ 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.?

准备完毕,现在开始。

显示按大小排序的RPM列表

该示例应用程序不是很复杂。它应该清晰地显示以下输出:

$ ./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

它应该还可让用户重新运行查询,同时覆盖匹配数量和包名称,以及按大小(字节)来排序。

现在一切准备就绪,你可以开始创建应用程序了。以下三个框架可供参考。

1. Rich

Rich 是威尔·麦克古根 (Will McGugan) 编写的一款极易使用的框架。它不提供大量widget小组件(一个仍在测试阶段,名为Textual的姐妹项目更注重组件。)

安装Rich

安装Rich框架:

$ pip install rich

这是我的Python脚本代码。它在清晰的表上生成进度条和结果:

#!/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)

为原始脚本添加表和进度条非常容易。

下面是全新改进后的文本UI的样子。

图1

2.Tkinter

Tkinter结合了多个框架:TCL、服务器租用TK和widget小组件(Ttk)。

该框架相当成熟,文档和示例完备。建议先主要遵循​​官方教程​​,在掌握了基础知识后,可以继续阅读感兴趣的其他教程。

有几点需要注意:

检查你的系统是否正确安装了Tkinter,如下所示:python -m tkinter。使用回调函数(command=),使你的GUI响应事件。Tkinter使用特殊变量进行通信,这些变量帮你跟踪更改(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

Doesnt 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

但是,这也意味着一旦脚本启动,就可以重新运行查询,只需在搜索选项框上修改参数。

3. DearPyGui

乔纳森·霍夫施塔特 (Jonathan Hoffstadt) 开发的DearPyGui可跨平台(Linux、Windows和macOS),具备一些出色的功能。云服务器

安装DearPyGui

如果你有当前系统(比如Fedora 33或Windows 10 Pro),安装起来应该很容易:

$ pip install dearpygui

以下是用DearPyGui重写的应用程序:

#!/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在嵌套组件时使用上下文,因而创建GUI时容易得多。代码也没有Tkinter代码那么冗长,对类型的支持也要更好(比如说,PyCharm提供了自动完成方法参数的功能)。

DearPyGui还很年轻(目前是版本1.0.3),也有一些bug,尤其是在旧的Linux发行版上。但它很有前景,正在积极开发中。

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

图3

原文标题:3 UI frameworks for writing user-friendly applications in Python,作者:Jose Vicente Nunez

copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap