我已经使用GTK3、Python和Glade将两个matplotlib图插入到一个容器中。他们使用来自第三方API的数据绘制区域设置的天气。我希望在输入新的区域设置并按下刷新按钮时更新图形。
下面的每一个解决方案都以不同的问题结束。太多了,无法描绘。通常,他们已经设法用新图表显示主窗口的另一个实例,但用旧图表显示的旧实例仍然打开。我试过:
-
销毁父容器,然后重新加载更新的容器。以下代码产生分段故障。代码试图将对象从生成器的新实例传递到旧实例。我认为这是我的主要问题。我不知道如何将正在使用的构建器实例传递到
on_refresh_button_click()
中,而不必将所有内容重写到一个类中。def on_refresh_button_click(self, widget): parent = self.get_parent() grandparent = parent.get_parent() parent.destroy() city_name = "yerevan" db = "test.db" get_updated_data(city_name) builder = builder_with_signals() read_weather_from_db(db, builder, city_name) grandparent.add(parent) parent.show_all()
-
使用
self.get_parent()
获取按钮的父容器作为要处理的对象。我认为这几乎奏效了。我可以将remove()
和destroy()
作为包含该图的容器。我想我也成功地添加了更新的一个。但我没能把它展示出来。代码:def on_refresh_button_click(self, widget): parent = self.get_parent() city_name = "yerevan" db = "test.db" get_updated_data(city_name) fig_list = read_weather_from_db(db, city_name) for child in parent: try: for grandchild in child: if Gtk.Buildable.get_name(grandchild) == "chart_past": parent = child # Confusing, yes. old = grandchild new = FigureCanvas(fig_list[0]) props = {} for key in parent.list_child_properties(): props[key.name] = parent.child_get_property(old, key.name) parent.remove(old) parent.add(new) for name, value in props.items(): parent.child_set_property(new, name, value) parent.show_all() child.show_all() grandchild.show_all() # Try to find the newly added object for item in parent: print("trying to find another:", Gtk.Buildable.get_name(item)) except: print(Gtk.Buildable.get_name(child))
-
删除旧的容器/小部件,然后添加一个新的,使用以下代码:
def replace_widget(old, new): parent = old.get_parent() props = {} for key in Gtk.ContainerClass.list_child_properties(type(parent)): props[key.name] = parent.child_get_property(old, key.name) parent.remove(old) parent.add(new) for name, value in props.iteritems(): parent.child_set_property(new, name, value)
-
在使用不同的区域设置从头开始运行脚本之前销毁主窗口:
def on_refresh_button_click(self, widget): builder = setup_builder() add_signals(builder) window = builder.get_object("window1") window.destroy() display_data("yerevan")
-
关闭程序并重新启动它,这甚至对我来说都没有意义,但:
def on_refresh_button_click(self, widget): Gtk.main_quit() display_data("yerevan")
-
从这里、这里和这里使用
canvas.draw()
。 -
用
add()
替换add_with_viewport()
,因为这说明它有问题。
还阅读了本文档的不同部分,并尝试了其他一些事情,但这两天太长了,所以我忘记了。
大多数示例似乎使用GTK3和Python构建应用程序,但没有Glade。他们也使用类。我(暂时(不想使用类。在我把整件事重写成一个类之前,我想看看是否有人知道解决方案。我可能只是误解或错过了什么。
我是GTK和Glade的新手,这是我的第一次尝试,所以请原谅这场混乱。我省略了SQLCRUD代码和向API发送请求的代码。这些效果很好。相关代码:
# SETUP THE BUILDER
def setup_builder():
return Gtk.Builder()
def add_signals(builder):
builder.add_objects_from_file('weather.xml', ('window1', 'refresh_button', 'box_charts'))
return builder.connect_signals({'on_window1_destroy': (on_window1_destroy,'window1'),
'on_refresh_button_click': (on_refresh_button_click,),
})
def builder_with_signals():
builder = setup_builder()
add_signals(builder)
return builder
# READ DATA FROM DATABASE
def read_weather_from_db(db, builder, city_name):
chart_future_values = read_db(db, "chart_future", city_name)
chart_past_values = read_db(db, "chart_past", city_name)
fig_future = embed_chart("day and time", "temp", chart_future_values["xticks"], chart_future_values["datetimes_x_axis"], chart_future_values["temps"])
fig_past = embed_chart("day and time", "temp", chart_past_values["xticks"], chart_past_values["datetimes_x_axis"], chart_past_values["temps"])
add_canvas(builder, "chart_future", fig_future)
add_canvas(builder, "chart_past", fig_past)
return builder
# EMBED THE CHARTS INTO CONTAINERS
def embed_chart(xlabel, ylabel, xticks, xticklabels, yticks):
fig = Figure(figsize=(5, 5), dpi=100)
chart = fig.add_subplot(111)
chart.set_xlabel(xlabel)
chart.set_ylabel(ylabel)
chart.set_xticks(xticks)
chart.set_xticklabels(xticklabels, rotation=90)
chart.plot(xticks, yticks)
return fig
def add_canvas(builder, chart_container, fig):
canvas = FigureCanvas(fig)
subbox_chart = builder.get_object(chart_container)
subbox_chart.add(canvas)
# THIS RUNS THE SCRIPT
def display_data(city_name="isfahan"):
get_updated_data(city_name)
builder = builder_with_signals()
read_weather_from_db("test.db", builder, city_name)
show_gtk(builder)
def on_window1_destroy(self, widget):
Gtk.main_quit()
# HERE IS THE IMPORTANT BIT
def on_refresh_button_click(self, widget):
# I DON'T KNOW WHAT TO PUT HERE
def show_gtk(builder):
window_main = builder.get_object('window1')
window_main.show_all()
Gtk.main()
我不认为你需要Glade xml文件,但我不确定,因为我对这个太陌生了:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="window1">
<property name="can_focus">False</property>
<signal name="destroy" handler="on_window1_destroy" swapped="no"/>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkBox" id="box_main">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="box_charts">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="chart_past">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_width">500</property>
<property name="min_content_height">500</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="chart_future">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_width">500</property>
<property name="min_content_height">500</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="refresh_button">
<property name="label" translatable="yes">refresh</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="button-press-event" handler="on_refresh_button_click" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
事实证明,使用self.parent()
作为起点可能是最理想的选择,因为我不想将整个事情重写为一个类。但是:
- 销毁/移除包含图形的子容器意味着现在没有更多的容器可以放入新图形
- 我认为我无法删除子容器中的图形。这种假设是错误的
删除图形需要花费一些精力。假设child
是包含容器对象的变量。打印child.get_children()
返回None
。这在一定程度上导致了我的错误假设。
但我注意到,当我尝试add()
一个更新的图时,它给了我这个错误:gtk_scrolled_window_add: assertion 'child_widget == NULL' failed
。里面有什么东西。
我看不到它,但我可以删除这个child_widget
吗?
# This successfully removes any descendent in `child`.
# It leaves the container empty, so to speak.
for grandkid in child.get_children():
child.remove(grandkid)
# This adds and displays the updated figure.
new = FigureCanvas(fig)
child.add(new)
child.show_all()
工作。
我不确定我能给你一个使用现有代码的例子,但我是如何做到的:
Figure = None
def invoice_chart_clicked (self, button):
global Figure
if Figure == None:
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
from matplotlib.pyplot import pie
self.figure = Figure(figsize=(4, 4), dpi=100)
canvas = FigureCanvas(self.figure) # a Gtk.DrawingArea
canvas.set_size_request(800, 500)
overlay = self.builder.get_object('overlay1')
overlay.add (canvas)
a = self.figure.add_subplot(111)
labels = list()
fractions = list()
unpaid = 0
self.cursor.execute("SELECT SUM(amount_due), c.name FROM invoices "
"JOIN contacts AS c ON c.id = invoices.customer_id "
"WHERE (canceled, paid, posted) = "
"(False, False, True) GROUP BY customer_id, c.name "
"ORDER BY SUM(amount_due)")
for row in self.cursor.fetchall():
customer_total = row[0]
customer_name = row[1]
fractions.append(customer_total)
labels.append(customer_name)
unpaid += 1
if unpaid == 0:
labels.append("None")
fractions.append(1.00)
a.pie(fractions, labels=labels, autopct='%1.f%%', radius=0.7)
window = self.builder.get_object('window1')
window.show_all()
每次重新加载此函数时,都会重新生成绘图。你可以在这里找到完整的代码。我从来没有运行过任何测试来查看所有内存是否都被正确释放,等等。也许它会提供足够的内存。
像这里一样创建一个单独的绘图窗口,GUI中的按钮将触发刷新?https://github.com/f4iteightiz/UWR_simulator一个连续运行的函数动画应该为您提供所需的"刷新"功能。