I've inserted two matplotlib graphs into a container using GTK3, Python, and Glade. They plot the weather of a locale using data from a third-party API. I want the graphs to update when I enter a new locale and press a refresh button.
Each of the solutions below ended with different problems. Too many to delineate. Generally, they've managed to display another instance of the main window with the new charts, but the old instance with the old charts remain open. I've tried:
Destroying the parent container and then reloading an updated one. The following code produces a segmentation fault. The code tries to pass an object from a new instance of the builder to the old one. I believe this to be my main problem. I don't know how to pass the instance of the builder I'm working with into
on_refresh_button_click()
without having to rewrite everything into a single class.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()
Using
self.get_parent()
to acquire the button's parent container as an object to work on. This almost worked, I think. I couldremove()
anddestroy()
the container containing the graph. And I think I also successfully added the updated one. But I couldn't get it to show. The code: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))
Removing the old container/widget and then adding a new one, using this code from here:
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)
Destroying the main window before running the script from scratch with a different locale:
def on_refresh_button_click(self, widget): builder = setup_builder() add_signals(builder) window = builder.get_object("window1") window.destroy() display_data("yerevan")
Closing the program and restarting it, which doesn't make sense even to me but:
def on_refresh_button_click(self, widget): Gtk.main_quit() display_data("yerevan")
Replacing
add_with_viewport()
withadd()
because this says it's a problem.
Also read different parts of this documentation and tried several other things but it's been a long two days so I forget.
Most examples seem to build applications with GTK3 and Python but without Glade. They also use classes. I don't want to use classes (for the moment). I'd like to see if anyone knows a solution before I rewrite the whole thing into a single class. I'm probably just misunderstanding or missing something.
I'm super new to GTK and Glade and this is my first attempt so pardon the mess. I've left out the SQL CRUD code and the code that sends requests to the API. Those work perfectly. The relevant code:
# 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()
I don't think you need the Glade xml file but I'm not sure because I'm so new to this:
<?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>