本帖最后由 damiaa 于 2025-3-6 21:18 编辑
【 STM32MP135F-DK测评】+(7)为制作图形界面通读python-gtk-3手册 之三
继续第7章到13章
7. 标签
标签是在窗口中放置不可编辑文本的主要方法,例如在 Gtk.Entry 小部件旁边放置标题。您可以在构造函数中指定文本,也可以稍后使用 Gtk.Label.set_text() 或 Gtk.Label.set_markup() 方法指定文本。
标签的宽度将自动调整。您可以通过在标签字符串中放置换行符 (“n”) 来生成多行标签。
标签可以使用 Gtk.Label.set_selectable() 进行选择。可选标签允许用户将标签内容复制到剪贴板。只有包含有用的复制信息(如错误消息)的标签才应设置为可选。
可以使用 Gtk.Label.set_justify() 方法对标签文本进行对齐。该 widget 还能够自动换行,这可以通过 Gtk.Label.set_line_wrap() 激活。
Gtk.Label 支持一些简单的格式,例如允许你将某些文本设为粗体、彩色或更大。您可以通过向 Gtk.Label.set_markup() 提供字符串来执行此作,,使用 Pango 标记语法 1.例如,<b>粗体文本</b>和<s>删除线文本</s>。此外,Gtk.Label 支持可点击的超链接。链接的标记是从 HTML 中借来的,使用带有 href 和 title 属性的 a。GTK+ 呈现的链接类似于它们在 Web 浏览器中的显示方式,带有彩色、带下划线的文本。title 属性在链接上显示为工具提示。label.set_markup(“转到 <a href=”https://www.gtk.org“ ”title=“我们的网站”>GTK+ 网站</a>了解更多“)标签可能包含助记词。助记词是标签中带下划线的字符,用于键盘导航。助记词的创建方法是在助记符之前提供带有下划线的字符串,例如“_File”,以 Gtk.Label.new_with_mnemonic() 或 Gtk.Label.set_text_with_mnemonic() () 开头。助记词会自动激活标签所在的任何可激活的小部件,例如 Gtk.Button;如果标签不在助记词的 Target 小部件内,则必须使用Gtk.Label.set_mnemonic_widget().告诉标签的目标。
7.1. Example
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class LabelWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Label Example")
hbox = Gtk.Box(spacing=10)
hbox.set_homogeneous(False)
vbox_left = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
vbox_left.set_homogeneous(False)
vbox_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
vbox_right.set_homogeneous(False)
hbox.pack_start(vbox_left, True, True, 0)
hbox.pack_start(vbox_right, True, True, 0)
label = Gtk.Label(label="This is a normal label")
vbox_left.pack_start(label, True, True, 0)
label = Gtk.Label()
label.set_text("This is a left-justified label.\nWith multiple lines.")
label.set_justify(Gtk.Justification.LEFT)
vbox_left.pack_start(label, True, True, 0)
label = Gtk.Label(
label="This is a right-justified label.\nWith multiple lines."
)
label.set_justify(Gtk.Justification.RIGHT)
vbox_left.pack_start(label, True, True, 0)
label = Gtk.Label(
label="This is an example of a line-wrapped label. It "
"should not be taking up the entire "
"width allocated to it, but automatically "
"wraps the words to fit.\n"
" It supports multiple paragraphs correctly, "
"and correctly adds "
"many extra spaces. "
)
label.set_line_wrap(True)
label.set_max_width_chars(32)
vbox_right.pack_start(label, True, True, 0)
label = Gtk.Label(
label="This is an example of a line-wrapped, filled label. "
"It should be taking "
"up the entire width allocated to it. "
"Here is a sentence to prove "
"my point. Here is another sentence. "
"Here comes the sun, do de do de do.\n"
" This is a new paragraph.\n"
" This is another newer, longer, better "
"paragraph. It is coming to an end, "
"unfortunately."
)
label.set_line_wrap(True)
label.set_justify(Gtk.Justification.FILL)
label.set_max_width_chars(32)
vbox_right.pack_start(label, True, True, 0)
label = Gtk.Label()
label.set_markup(
"Text can be <small>small</small>, <big>big</big>, "
"<b>bold</b>, <i>italic</i> and even point to "
'somewhere in the <a href="https://www.gtk.org" '
'title="Click to find out more">internets</a>.'
)
label.set_line_wrap(True)
label.set_max_width_chars(48)
vbox_left.pack_start(label, True, True, 0)
label = Gtk.Label.new_with_mnemonic(
"_Press Alt + P to select button to the right"
)
vbox_left.pack_start(label, True, True, 0)
label.set_selectable(True)
button = Gtk.Button(label="Click at your own risk")
label.set_mnemonic_widget(button)
vbox_right.pack_start(button, True, True, 0)
self.add(hbox)
window = LabelWindow()
window.connect("destroy", Gtk.main_quit)
window.show_all()
Gtk.main()
8. Entry 输入框
Entry 小部件允许用户输入文本。您可以使用 Gtk.Entry.set_text() 方法更改内容,并使用 Gtk.Entry.get_text() 方法读取当前内容。您还可以通过调用 Gtk.Entry.set_max_length() 来限制 Entry 可以采用的字符数。
有时,您可能希望将 Entry 小部件设置为只读。这可以通过将 False 传递给 Gtk.Entry.set_editable() 方法来完成。
Entry 小部件还可用于从用户那里检索密码。通常的做法是隐藏在条目中键入的字符,以防止将密码泄露给第三方。使用 False 调用 Gtk.Entry.set_visibility() 将导致文本被隐藏。
Gtk.Entry 能够在文本后面显示进度或活动信息。这类似于 Gtk.ProgressBar 小部件,常见于 Web 浏览器中,用于指示已完成多少页面下载。要使条目显示此类信息,请、使用 Gtk.Entry.set_progress_fraction()、Gtk.Entry.set_progress_pulse_step() 或 Gtk.Entry.progress_pulse()。此外,Entry 可以在条目的任一侧显示图标。这些图标可以通过单击来激活,可以设置为拖动源,并且可以具有工具提示。要添加图标,请使用 Gtk.Entry.set_icon_from_icon_name() 或根据图标名称、pixbuf 或图标主题设置图标的各种其他函数之一。要在图标上设置工具提示,请使用 Gtk.Entry.set_icon_tooltip_text() 或相应的标记函数。
8.1. 示例
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
class EntryWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Entry Demo")
self.set_size_request(200, 100)
self.timeout_id = None
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(vbox)
self.entry = Gtk.Entry()
self.entry.set_text("Hello World")
vbox.pack_start(self.entry, True, True, 0)
hbox = Gtk.Box(spacing=6)
vbox.pack_start(hbox, True, True, 0)
self.check_editable = Gtk.CheckButton(label="Editable")
self.check_editable.connect("toggled", self.on_editable_toggled)
self.check_editable.set_active(True)
hbox.pack_start(self.check_editable, True, True, 0)
self.check_visible = Gtk.CheckButton(label="Visible")
self.check_visible.connect("toggled", self.on_visible_toggled)
self.check_visible.set_active(True)
hbox.pack_start(self.check_visible, True, True, 0)
self.pulse = Gtk.CheckButton(label="Pulse")
self.pulse.connect("toggled", self.on_pulse_toggled)
self.pulse.set_active(False)
hbox.pack_start(self.pulse, True, True, 0)
self.icon = Gtk.CheckButton(label="Icon")
self.icon.connect("toggled", self.on_icon_toggled)
self.icon.set_active(False)
hbox.pack_start(self.icon, True, True, 0)
def on_editable_toggled(self, button):
value = button.get_active()
self.entry.set_editable(value)
def on_visible_toggled(self, button):
value = button.get_active()
self.entry.set_visibility(value)
def on_pulse_toggled(self, button):
if button.get_active():
self.entry.set_progress_pulse_step(0.2)
# Call self.do_pulse every 100 ms
self.timeout_id = GLib.timeout_add(100, self.do_pulse, None)
else:
# Don't call self.do_pulse anymore
GLib.source_remove(self.timeout_id)
self.timeout_id = None
self.entry.set_progress_pulse_step(0)
def do_pulse(self, user_data):
self.entry.progress_pulse()
return True
def on_icon_toggled(self, button):
if button.get_active():
icon_name = "system-search-symbolic"
else:
icon_name = None
self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, icon_name)
win = EntryWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
9. 按钮小部件
9.1. 按钮
按钮 Widget 是另一个常用的 Widget。它通常用于附加一个在按下按钮时调用的函数。
Gtk.Button 小部件可以容纳任何有效的子小部件。也就是说,它可以容纳大多数其他标准 Gtk.Widget。最常用的 child 是 Gtk.Label。
通常,您希望连接到按钮的 “clicked” 信号,该信号在按下和释放按钮时发出。
9.1.1. Example
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class ButtonWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Button Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=6)
self.add(hbox)
button = Gtk.Button.new_with_label("Click Me")
button.connect("clicked", self.on_click_me_clicked)
hbox.pack_start(button, True, True, 0)
button = Gtk.Button.new_with_mnemonic("_Open")
button.connect("clicked", self.on_open_clicked)
hbox.pack_start(button, True, True, 0)
button = Gtk.Button.new_with_mnemonic("_Close")
button.connect("clicked", self.on_close_clicked)
hbox.pack_start(button, True, True, 0)
def on_click_me_clicked(self, button):
print('"Click me" button was clicked')
def on_open_clicked(self, button):
print('"Open" button was clicked')
def on_close_clicked(self, button):
print("Closing application")
Gtk.main_quit()
win = ButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
9.2. ToggleButton
Gtk.ToggleButton 与普通的 Gtk.Button 非常相似,但是当单击时,它们会保持激活或按下状态,直到再次单击。当按钮的状态发生变化时,会发出 “toggled” 信号。
要检索 Gtk.ToggleButton 的状态,你可以使用 Gtk.ToggleButton.get_active() 方法。如果按钮为 “down”,则返回 True。您还可以使用 Gtk.ToggleButton.set_active() 设置切换按钮的状态。请注意,如果你这样做,并且 state 实际上发生了变化,它会导致发出 “toggled” 信号。
9.2.1. 示例
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class ToggleButtonWindow(Gtk.Window):
def __init__(self):
super().__init__(title="ToggleButton Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=6)
self.add(hbox)
button = Gtk.ToggleButton(label="Button 1")
button.connect("toggled", self.on_button_toggled, "1")
hbox.pack_start(button, True, True, 0)
button = Gtk.ToggleButton(label="B_utton 2", use_underline=True)
button.set_active(True)
button.connect("toggled", self.on_button_toggled, "2")
hbox.pack_start(button, True, True, 0)
def on_button_toggled(self, button, name):
if button.get_active():
state = "on"
else:
state = "off"
print("Button", name, "was turned", state)
win = ToggleButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
9.3. CheckButton
Gtk.CheckButton 继承自 Gtk.ToggleButton。两者之间唯一真正的区别是 Gtk.CheckButton 的外观。Gtk.CheckButton 将一个离散的 Gtk.ToggleButton 放在一个小部件旁边(通常是 Gtk.Label)。“toggled” 信号 Gtk.ToggleButton.set_active() 和 Gtk.ToggleButton.get_active() 是继承的。
9.4. 单选按钮 RadioButton
和复选框一样,单选按钮也继承自 Gtk.ToggleButton,但是这些按钮是成组工作的,并且任何时候都只能选择一个组中的一个 Gtk.RadioButton。因此,Gtk.RadioButton 是为用户提供许多选项之一的方法。
可以使用静态方法 Gtk.RadioButton.new_from_widget()、Gtk.RadioButton.new_with_label_from_widget() 或 Gtk.RadioButton.new_with_mnemonic_from_widget() 之一创建单选按钮。将创建组中的第一个单选按钮,并将 None 作为组参数传递。在后续调用中,要添加此按钮的组应作为参数传递。
首次运行时,组中的第一个单选按钮将处于活动状态。这可以通过调用 Gtk.ToggleButton.set_active() 并将 True 作为第一个参数来更改。
在创建 Gtk.RadioButton 的小部件组后,可以通过调用 Gtk.RadioButton.join_group() 来实现。
9.4.1. Example
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class RadioButtonWindow(Gtk.Window):
def __init__(self):
super().__init__(title="RadioButton Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=6)
self.add(hbox)
button1 = Gtk.RadioButton.new_with_label_from_widget(None, "Button 1")
button1.connect("toggled", self.on_button_toggled, "1")
hbox.pack_start(button1, False, False, 0)
button2 = Gtk.RadioButton.new_from_widget(button1)
button2.set_label("Button 2")
button2.connect("toggled", self.on_button_toggled, "2")
hbox.pack_start(button2, False, False, 0)
button3 = Gtk.RadioButton.new_with_mnemonic_from_widget(button1, "B_utton 3")
button3.connect("toggled", self.on_button_toggled, "3")
hbox.pack_start(button3, False, False, 0)
def on_button_toggled(self, button, name):
if button.get_active():
state = "on"
else:
state = "off"
print("Button", name, "was turned", state)
win = RadioButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
9.5. LinkButton
Gtk.LinkButton 是一个带有超链接的 Gtk.Button,类似于 Web 浏览器使用的超链接,单击时会触发一个作。显示指向资源的快速链接非常有用。
绑定到 Gtk.LinkButton 的 URI 可以使用 Gtk.LinkButton.set_uri() 进行专门设置,并使用 Gtk.LinkButton.get_uri() 进行检索。
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class LinkButtonWindow(Gtk.Window):
def __init__(self):
super().__init__(title="LinkButton Demo")
self.set_border_width(10)
button = Gtk.LinkButton.new_with_label(
uri="https://www.gtk.org",
label="Visit GTK+ Homepage"
)
self.add(button)
win = LinkButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
9.6. SpinButton
Gtk.SpinButton 是允许用户设置某个属性值的理想方式。Gtk.SpinButton 允许用户单击两个箭头之一来增加或减少显示的值,而不必直接在 Gtk.Entry 中键入数字。仍然可以键入值,但可以检查该值以确保它在给定范围内。Gtk.SpinButton 的主要属性是通过 Gtk.Adjust 设置的。
要更改 Gtk.SpinButton 显示的值,请使用 Gtk.SpinButton.set_value()。输入的值可以是整数或浮点数,根据您的要求,请分别使用 Gtk.SpinButton.get_value_as_int() 或 Gtk.SpinButton.get_value()。
当您允许在旋转按钮中显示 float 值时,您可能希望通过调用 Gtk.SpinButton.set_digits() 来调整显示的小数点空格数。
默认情况下,Gtk.SpinButton 接受文本数据。如果您希望将其限制为仅数值,请使用 True 作为参数调用 Gtk.SpinButton.set_numeric() 。我们还可以调整 Gtk.SpinButton 的更新策略。这里有两个选项;默认情况下,即使输入的数据无效,旋转按钮也会更新该值。或者,我们可以通过调用 Gtk.SpinButton.set_update_policy() 将策略设置为仅在输入的值有效时更新
9.6.1. 示例
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class SpinButtonWindow(Gtk.Window):
def __init__(self):
super().__init__(title="SpinButton Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=6)
self.add(hbox)
adjustment = Gtk.Adjustment(upper=100, step_increment=1, page_increment=10)
self.spinbutton = Gtk.SpinButton()
self.spinbutton.set_adjustment(adjustment)
self.spinbutton.connect("value-changed", self.on_value_changed)
hbox.pack_start(self.spinbutton, False, False, 0)
check_numeric = Gtk.CheckButton(label="Numeric")
check_numeric.connect("toggled", self.on_numeric_toggled)
hbox.pack_start(check_numeric, False, False, 0)
check_ifvalid = Gtk.CheckButton(label="If Valid")
check_ifvalid.connect("toggled", self.on_ifvalid_toggled)
hbox.pack_start(check_ifvalid, False, False, 0)
def on_value_changed(self, scroll):
print(self.spinbutton.get_value_as_int())
def on_numeric_toggled(self, button):
self.spinbutton.set_numeric(button.get_active())
def on_ifvalid_toggled(self, button):
if button.get_active():
policy = Gtk.SpinButtonUpdatePolicy.IF_VALID
else:
policy = Gtk.SpinButtonUpdatePolicy.ALWAYS
self.spinbutton.set_update_policy(policy)
win = SpinButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
9.7. Switch开关
Gtk.Switch 是一个具有两种状态的小部件:on 或 off。用户可以通过单击空白区域或拖动手柄来控制哪个状态应处于活动状态。
你不应该在 Gtk.Switch 上使用 “activate” 信号,它是一个动作信号,发出它会导致 switch 动画化。应用程序永远不应该连接到这个信号,而是使用 “notify::active” 信号,请参阅下面的示例。
9.7.1. 示例
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class SwitcherWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Switch Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=6)
self.add(hbox)
switch = Gtk.Switch()
switch.connect("notify::active", self.on_switch_activated)
switch.set_active(False)
hbox.pack_start(switch, True, True, 0)
switch = Gtk.Switch()
switch.connect("notify::active", self.on_switch_activated)
switch.set_active(True)
hbox.pack_start(switch, True, True, 0)
def on_switch_activated(self, switch, gparam):
if switch.get_active():
state = "on"
else:
state = "off"
print("Switch was turned", state)
win = SwitcherWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
10. Expander扩展器
扩展器允许在窗口或对话框中动态隐藏或显示信息。扩展器可以采用单个小部件,该小部件将在展开时显示。
扩展器将保持展开状态,直到再次单击。当 Expander 的状态发生变化时,会发出 “activate” 信号。
可以通过将 True 或 False 传递给 Gtk.Expander.set_expanded() 来以编程方式展开或折叠扩展器。请注意,这样做会导致发出 “activate” 信号。
可以通过将它们附加到 Gtk.Box 来添加多个小部件,例如 Gtk.Label 和 Gtk.Button。
10.1. 示例
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class ExpanderExample(Gtk.Window):
def __init__(self):
super().__init__(title="Expander Demo")
self.set_size_request(350, 100)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(vbox)
text_expander = Gtk.Expander(
label="This expander displays additional information"
)
text_expander.set_expanded(True)
vbox.add(text_expander)
msg = """
This message is quite long, complicated even:
- It has a list with a sublist:
- of 3 elements;
- taking several lines;
- with indentation.
"""
details = Gtk.Label(label=msg)
text_expander.add(details)
widget_expander = Gtk.Expander(label="Expand for more controls")
vbox.add(widget_expander)
expander_hbox = Gtk.HBox()
widget_expander.add(expander_hbox)
expander_hbox.add(Gtk.Label(label="Text message"))
expander_hbox.add(Gtk.Button(label="Click me"))
self.show_all()
win = ExpanderExample()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
11. ProgressBar
Gtk.ProgressBar 通常用于显示长时间运行的作的进度。它提供了一个视觉线索,表明处理正在进行中。Gtk.ProgressBar 可以在两种不同的模式下使用:百分比模式和活动模式。
当应用程序可以确定需要进行多少工作(例如,从文件中读取固定数量的字节)并可以监控其进度时,它可以在百分比模式下使用 Gtk.ProgressBar,并且用户会看到一个不断增长的条形图,指示已完成工作的百分比。在此模式下,应用程序需要定期调用 Gtk.ProgressBar.set_fraction() 以更新进度条,并传递介于 0 和 1 之间的浮点数以提供新的百分比值。
当应用程序无法准确了解要完成的工作量时,它可以使用 activity 模式,该模式通过在进度区域内来回移动的块来显示活动。在此模式下,应用程序需要定期调用 Gtk.ProgressBar.pulse() 来更新进度条。您还可以使用 Gtk.ProgressBar.set_pulse_step() 方法选择步长。
默认情况下,Gtk.ProgressBar 是水平的,从左到右,但你可以使用 Gtk.ProgressBar.set_orientation() 方法将其更改为垂直进度条。可以使用 Gtk.ProgressBar.set_inverted() 来更改进度条的增长方向。Gtk.ProgressBar 也可以包含文本,可以通过调用 Gtk.ProgressBar.set_text() 和 Gtk.ProgressBar.set_show_text() 来设置。
11.1. Example
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
class ProgressBarWindow(Gtk.Window):
def __init__(self):
super().__init__(title="ProgressBar Demo")
self.set_border_width(10)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(vbox)
self.progressbar = Gtk.ProgressBar()
vbox.pack_start(self.progressbar, True, True, 0)
button = Gtk.CheckButton(label="Show text")
button.connect("toggled", self.on_show_text_toggled)
vbox.pack_start(button, True, True, 0)
button = Gtk.CheckButton(label="Activity mode")
button.connect("toggled", self.on_activity_mode_toggled)
vbox.pack_start(button, True, True, 0)
button = Gtk.CheckButton(label="Right to Left")
button.connect("toggled", self.on_right_to_left_toggled)
vbox.pack_start(button, True, True, 0)
self.timeout_id = GLib.timeout_add(50, self.on_timeout, None)
self.activity_mode = False
def on_show_text_toggled(self, button):
show_text = button.get_active()
if show_text:
text = "some text"
else:
text = None
self.progressbar.set_text(text)
self.progressbar.set_show_text(show_text)
def on_activity_mode_toggled(self, button):
self.activity_mode = button.get_active()
if self.activity_mode:
self.progressbar.pulse()
else:
self.progressbar.set_fraction(0.0)
def on_right_to_left_toggled(self, button):
value = button.get_active()
self.progressbar.set_inverted(value)
def on_timeout(self, user_data):
"""
Update value on the progress bar
"""
if self.activity_mode:
self.progressbar.pulse()
else:
new_value = self.progressbar.get_fraction() + 0.01
if new_value > 1:
new_value = 0
self.progressbar.set_fraction(new_value)
# As this is a timeout function, return True so that it
# continues to get called
return True
win = ProgressBarWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
12. Spinner
Gtk.Spinner 显示图标大小的旋转动画。它通常被用作 GtkProgressBar 的替代方案,用于显示无限期活动,而不是实际进度。
要启动动画,请使用 Gtk.Spinner.start(),要停止它,请使用 Gtk.Spinner.stop()。
12.1. 示例
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class SpinnerAnimation(Gtk.Window):
def __init__(self):
super().__init__(title="Spinner")
self.set_border_width(3)
self.connect("destroy", Gtk.main_quit)
self.button = Gtk.ToggleButton(label="Start Spinning")
self.button.connect("toggled", self.on_button_toggled)
self.button.set_active(False)
self.spinner = Gtk.Spinner()
self.grid = Gtk.Grid()
self.grid.add(self.button)
self.grid.attach_next_to(
self.spinner, self.button, Gtk.PositionType.BOTTOM, 1, 2
)
self.grid.set_row_homogeneous(True)
self.add(self.grid)
self.show_all()
def on_button_toggled(self, button):
if button.get_active():
self.spinner.start()
self.button.set_label("Stop Spinning")
else:
self.spinner.stop()
self.button.set_label("Start Spinning")
myspinner = SpinnerAnimation()
Gtk.main()
12.2. Extended example
一个扩展示例,它使用 timeout 函数来启动和停止旋转动画。定期调用 on_timeout() 函数,直到它返回 False,此时超时将自动销毁,并且不会再次调用该函数。
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
class SpinnerWindow(Gtk.Window):
def __init__(self, *args, **kwargs):
super().__init__(title="Spinner Demo")
self.set_border_width(10)
mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(mainBox)
self.spinner = Gtk.Spinner()
mainBox.pack_start(self.spinner, True, True, 0)
self.label = Gtk.Label()
mainBox.pack_start(self.label, True, True, 0)
self.entry = Gtk.Entry()
self.entry.set_text("10")
mainBox.pack_start(self.entry, True, True, 0)
self.buttonStart = Gtk.Button(label="Start timer")
self.buttonStart.connect("clicked", self.on_buttonStart_clicked)
mainBox.pack_start(self.buttonStart, True, True, 0)
self.buttonStop = Gtk.Button(label="Stop timer")
self.buttonStop.set_sensitive(False)
self.buttonStop.connect("clicked", self.on_buttonStop_clicked)
mainBox.pack_start(self.buttonStop, True, True, 0)
self.timeout_id = None
self.connect("destroy", self.on_SpinnerWindow_destroy)
def on_buttonStart_clicked(self, widget, *args):
""" Handles "clicked" event of buttonStart. """
self.start_timer()
def on_buttonStop_clicked(self, widget, *args):
""" Handles "clicked" event of buttonStop. """
self.stop_timer("Stopped from button")
def on_SpinnerWindow_destroy(self, widget, *args):
""" Handles destroy event of main window. """
# ensure the timeout function is stopped
if self.timeout_id:
GLib.source_remove(self.timeout_id)
self.timeout_id = None
Gtk.main_quit()
def on_timeout(self, *args, **kwargs):
""" A timeout function.
Return True to stop it.
This is not a precise timer since next timeout
is recalculated based on the current time."""
self.counter -= 1
if self.counter <= 0:
self.stop_timer("Reached time out")
return False
self.label.set_label("Remaining: " + str(int(self.counter / 4)))
return True
def start_timer(self):
""" Start the timer. """
self.buttonStart.set_sensitive(False)
self.buttonStop.set_sensitive(True)
# time out will check every 250 milliseconds (1/4 of a second)
self.counter = 4 * int(self.entry.get_text())
self.label.set_label("Remaining: " + str(int(self.counter / 4)))
self.spinner.start()
self.timeout_id = GLib.timeout_add(250, self.on_timeout, None)
def stop_timer(self, alabeltext):
""" Stop the timer. """
if self.timeout_id:
GLib.source_remove(self.timeout_id)
self.timeout_id = None
self.spinner.stop()
self.buttonStart.set_sensitive(True)
self.buttonStop.set_sensitive(False)
self.label.set_label(alabeltext)
win = SpinnerWindow()
win.show_all()
Gtk.main()
13. Tree and List Widgets Tree 和 List 小部件
Gtk.TreeView 及其关联的小部件是一种非常强大的数据显示方式。它们与 Gtk.ListStore 或 Gtk.TreeStore 结合使用,并提供一种以多种方式显示和作数据的方法,包括:
在添加、删除或编辑数据时自动更新
拖放支持
对数据进行排序
嵌入 widget,例如复选框、进度条等。
可重新排序和调整大小的列
筛选数据
Gtk.TreeView 的强大功能和灵活性带来了复杂性。由于所需的方法数量众多,初学者开发人员通常很难正确使用它。
13.1. The Model
每个 Gtk.TreeView 都有一个关联的 Gtk.TreeModel,其中包含 TreeView 显示的数据。每个 Gtk.TreeModel 可以被多个 Gtk.TreeView 使用。例如,这允许同时以 2 种不同的方式显示和编辑相同的基础数据。或者,这两个视图可能会显示来自同一模型数据的不同列,就像 2 个 SQL 查询(或“视图”)可能显示来自同一数据库表的不同字段一样。
虽然理论上你可以实现自己的模型,但你通常会使用 Gtk.ListStore 或 Gtk.TreeStore 模型类。Gtk.ListStore 包含简单的数据行,每行没有子行,而 Gtk.TreeStore 包含数据行,每行都可以有子行。
在构造模型时,您必须为模型包含的每个列指定数据类型。
store = Gtk.ListStore(str, str, float)
这将创建一个包含三列、两个字符串列和一个浮点列的列表存储。
使用 Gtk.ListStore.append() 或 Gtk.TreeStore.append() 向模型添加数据,具体取决于创建的模型类型。
对于 Gtk.ListStore:
treeiter = store.append(["The Art of Computer Programming", "Donald E. Knuth", 25.46])
对于 Gtk.TreeStore,您必须使用 Gtk.TreeIter 指定一个现有行以将新行附加到该行,或者为树的顶层指定 None:
treeiter = store.append(None, ["The Art of Computer Programming",
"Donald E. Knuth", 25.46])
两种方法都返回一个 Gtk.TreeIter 实例,该实例指向新插入的行的位置。你可以通过调用 Gtk.TreeModel.get_iter() 来检索 Gtk.TreeIter。
插入数据后,您可以使用树迭代器和列索引检索或修改数据。
print(store[treeiter][2]) # Prints value of third column
store[treeiter][2] = 42.15
与 Python 的内置列表对象一样,您可以使用 len() 来获取行数,并使用切片来检索或设置值。
# Print number of rows
print(len(store))
# Print all but first column
print(store[treeiter][1:])
# Print last column
print(store[treeiter][-1])
# Set last two columns
store[treeiter][1:] = ["Donald Ervin Knuth", 41.99]
迭代树模型的所有行也非常简单。
for row in store:
# Print values of all columns
print(row[:])
请记住,如果你使用 Gtk.TreeStore,上面的代码只会迭代顶层的行,而不会迭代节点的子节点。要遍历所有行,请使用 Gtk.TreeModel.foreach()。
def print_row(store, treepath, treeiter):
print("\t" * (treepath.get_depth() - 1), store[treeiter][:], sep="")
store.foreach(print_row)
除了使用上面提到的类似列表的方法访问存储在 Gtk.TreeModel 中的值外,还可以使用 Gtk.TreeIter 或 Gtk.TreePath 实例。两者都引用树模型中的特定行。可以通过调用 Gtk.TreeModel.get_iter() 将路径转换为迭代器。由于 Gtk.ListStore 只包含一个级别,即节点没有任何子节点,因此路径本质上是你要访问的行的索引。
# Get path pointing to 6th row in list store
path = Gtk.TreePath(5)
treeiter = liststore.get_iter(path)
# Get value at 2nd column
value = liststore.get_value(treeiter, 1)
在 Gtk.TreeStore 的情况下,路径是索引列表或字符串。字符串形式是一个由冒号分隔的数字列表。每个数字都是指该级别的偏移量。因此,路径 “0” 是指根节点,路径 “2:4” 是指第三个节点的第五个子节点。
# Get path pointing to 5th child of 3rd row in tree store
path = Gtk.TreePath([2, 4])
treeiter = treestore.get_iter(path)
# Get value at 2nd column
value = treestore.get_value(treeiter, 1)
Gtk.TreePath 的实例可以像列表一样访问,即 len(treepath) 返回 treepath 指向的项目深度,而 treepath 返回第 i 级的子索引。
13.2. The View
虽然有几种不同的模型可供选择,但只有一个视图小部件需要处理。它适用于列表或树存储。设置 Gtk.TreeView 并不是一件难事。它需要一个 Gtk.TreeModel 来知道从哪里检索它的数据,通过将其传递给 Gtk.TreeView 构造函数,或者通过调用 Gtk.TreeView.set_model() 来。
tree = Gtk.TreeView(model=store)
一旦 Gtk.TreeView 小部件有一个模型,它就需要知道如何显示模型。它通过列和单元格渲染器来实现这一点。headers_visible 控制是否显示列标题。
单元格渲染器用于以特定方式绘制树模型中的数据。GTK+ 附带了许多单元格渲染器,例如 Gtk.CellRendererText、Gtk.CellRendererPixbuf 和 Gtk.CellRendererToggle。此外,通过子类化 Gtk.CellRenderer 并使用 GObject.Property() 添加属性,自己编写自定义渲染器相对容易。
Gtk.TreeViewColumn 是 Gtk.TreeView 用来组织树状视图中的垂直列并保存一个或多个单元格渲染器的对象。如果 Gtk.TreeView 显示列标题,则每列都可能有一个标题。通过使用关键字 arguments 将模型映射到列,其中渲染器的属性作为标识符,模型列的索引作为参数。
renderer = Gtk.CellRendererPixbuf()
column = Gtk.TreeViewColumn(cell_renderer=renderer, icon_name=3)
tree.append_column(column)
位置参数可用于列标题和渲染器。
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Title", renderer, text=0, weight=1)
tree.append_column(column)
要在视图列中渲染多个模型列,你需要创建一个 Gtk.TreeViewColumn 实例并使用 Gtk.TreeViewColumn.pack_start() 将模型列添加到其中。
column = Gtk.TreeViewColumn("Title and Author")
title = Gtk.CellRendererText()
author = Gtk.CellRendererText()
column.pack_start(title, True)
column.pack_start(author, True)
column.add_attribute(title, "text", 0)
column.add_attribute(author, "text", 1)
tree.append_column(column)
13.3. The Selection
大多数应用程序不仅需要处理显示数据,还需要接收来自用户的输入事件。为此,只需获取对选择对象的引用并连接到 “changed” 信号。
select = tree.get_selection()
select.connect("changed", on_tree_selection_changed)
然后,要检索所选行的数据,请执行以下作:
def on_tree_selection_changed(selection):
model, treeiter = selection.get_selected()
if treeiter is not None:
print("You selected", model[treeiter][0])
您可以通过调用 Gtk.TreeSelection.set_mode() 来控制允许的选择。如果选择模式设置为 Gtk.SelectionMode.MULTIPLE,Gtk.TreeSelection.get_selected() 不起作用,请使用 Gtk.TreeSelection.get_selected_rows() 代替。
13.4. Sorting
排序是树视图的一个重要功能,并且由标准树模型(Gtk.TreeStore 和 Gtk.ListStore)支持,它们实现了 Gtk.TreeSortable 接口。
13.4.1. 通过单击列进行排序
Gtk.TreeView 的列可以通过调用 Gtk.TreeViewColumn.set_sort_column_id() 轻松排序。之后,可以通过单击其标题对列进行排序。
首先,我们需要一个简单的 Gtk.TreeView 和一个 Gtk.ListStore 作为模型。
model = Gtk.ListStore(str)
model.append(["Benjamin"])
model.append(["Charles"])
model.append(["alfred"])
model.append(["Alfred"])
model.append(["David"])
model.append(["charles"])
model.append(["david"])
model.append(["benjamin"])
treeView = Gtk.TreeView(model=model)
cellRenderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Title", renderer, text=0)
下一步是启用排序。请注意,column_id(示例中为 0)是指模型的列,而不是 TreeView 的列。
column.set_sort_column_id(0)
13.4.2. 设置自定义排序函数
也可以设置自定义比较函数以更改排序行为。例如,我们将创建一个区分大小写排序的比较函数。在上面的示例中,排序列表如下所示:
alfred
Alfred
benjamin
Benjamin
charles
Charles
david
David
区分大小写的排序列表将如下所示:
Alfred
Benjamin
Charles
David
alfred
benjamin
charles
david
首先,需要一个比较函数。此函数有两行,如果第一行应该在第二行之前,则必须返回一个负整数,如果它们相等,则必须返回零,如果第二行应该在第一行之前,则必须返回一个正整数。
def compare(model, row1, row2, user_data):
sort_column, _ = model.get_sort_column_id()
value1 = model.get_value(row1, sort_column)
value2 = model.get_value(row2, sort_column)
if value1 < value2:
return -1
elif value1 == value2:
return 0
else:
return 1
然后 sort 函数必须由 Gtk.TreeSortable.set_sort_func() 设置。
model.set_sort_func(0, compare, None)
13.5. Filtering
与排序不同,过滤不是由我们之前看到的两个模型处理的,而是由 Gtk.TreeModelFilter 类处理的。这个类,就像 Gtk.TreeStore 和 Gtk.ListStore 一样,是一个 Gtk.TreeModel。它充当“真实”模型(Gtk.TreeStore 或 Gtk.ListStore)之间的一个层,将一些元素隐藏到视图中。在实践中,它为 Gtk.TreeView 提供了底层模型的子集。Gtk.TreeModelFilter 的实例可以一个堆叠到另一个实例上,以便在同一个模型上使用多个过滤器(就像你在 SQL 请求中使用 “AND” 子句一样)。它们也可以与 Gtk.TreeModelSort 实例链接。
你可以创建一个 Gtk.TreeModelFilter 的新实例,并给它一个要过滤的模型,但最简单的方法是使用 Gtk.TreeModel.filter_new() 方法直接从过滤的模型中生成它。
filter = model.filter_new()
与排序函数的工作方式相同,Gtk.TreeModelFilter 使用“可见性”函数,给定底层模型中的一行,将返回一个布尔值,指示是否应该过滤掉该行。它由 Gtk.TreeModelFilter.set_visible_func() 设置:
filter.set_visible_func(filter_func, data=None)
“可见性”函数的替代方法是使用模型中的布尔列来指定要筛选的行。使用 Gtk.TreeModelFilter.set_visible_column() 选择哪列。
让我们看一个完整的例子,它使用了整个 Gtk.ListStore - Gtk.TreeModelFilter - Gtk.TreeModelFilter - Gtk.TreeView 堆栈。
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# list of tuples for each software, containing the software name, initial release, and main programming languages used
software_list = [
("Firefox", 2002, "C++"),
("Eclipse", 2004, "Java"),
("Pitivi", 2004, "Python"),
("Netbeans", 1996, "Java"),
("Chrome", 2008, "C++"),
("Filezilla", 2001, "C++"),
("Bazaar", 2005, "Python"),
("Git", 2005, "C"),
("Linux Kernel", 1991, "C"),
("GCC", 1987, "C"),
("Frostwire", 2004, "Java"),
]
class TreeViewFilterWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Treeview Filter Demo")
self.set_border_width(10)
# Setting up the self.grid in which the elements are to be positioned
self.grid = Gtk.Grid()
self.grid.set_column_homogeneous(True)
self.grid.set_row_homogeneous(True)
self.add(self.grid)
# Creating the ListStore model
self.software_liststore = Gtk.ListStore(str, int, str)
for software_ref in software_list:
self.software_liststore.append(list(software_ref))
self.current_filter_language = None
# Creating the filter, feeding it with the liststore model
self.language_filter = self.software_liststore.filter_new()
# setting the filter function, note that we're not using the
self.language_filter.set_visible_func(self.language_filter_func)
# creating the treeview, making it use the filter as a model, and adding the columns
self.treeview = Gtk.TreeView(model=self.language_filter)
for i, column_title in enumerate(
["Software", "Release Year", "Programming Language"]
):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
self.treeview.append_column(column)
# creating buttons to filter by programming language, and setting up their events
self.buttons = list()
for prog_language in ["Java", "C", "C++", "Python", "None"]:
button = Gtk.Button(label=prog_language)
self.buttons.append(button)
button.connect("clicked", self.on_selection_button_clicked)
# setting up the layout, putting the treeview in a scrollwindow, and the buttons in a row
self.scrollable_treelist = Gtk.ScrolledWindow()
self.scrollable_treelist.set_vexpand(True)
self.grid.attach(self.scrollable_treelist, 0, 0, 8, 10)
self.grid.attach_next_to(
self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1
)
for i, button in enumerate(self.buttons[1:]):
self.grid.attach_next_to(
button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1
)
self.scrollable_treelist.add(self.treeview)
self.show_all()
def language_filter_func(self, model, iter, data):
"""Tests if the language in the row is the one in the filter"""
if (
self.current_filter_language is None
or self.current_filter_language == "None"
):
return True
else:
return model[iter][2] == self.current_filter_language
def on_selection_button_clicked(self, widget):
"""Called on any of the button clicks"""
# we set the current language filter to the button's label
self.current_filter_language = widget.get_label()
print("%s language selected!" % self.current_filter_language)
# we update the filter, which updates in turn the view
self.language_filter.refilter()
win = TreeViewFilterWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
好先到这里。谢谢