I recently started developing some simple GUIs using Python GTK and Glade. I've created a GUI which consists of a main window with a button while the action of pressing the button is the popup of a second window with a matplotlib plot on it.
The problem I face is that when i close the second window then the first one is also closed and i would like to be able and terminate them separately.
Below is the python code and here is the glade file with GUI's layout.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import numpy as np
from matplotlib.figure import Figure
from matplotlib.axes import Subplot
from matplotlib.backends.backend_gtk3agg import (
FigureCanvasGTK3Agg as FigureCanvas)
class PlotApp:
def __init__(self):
gladefile = 'GTK-two-windows.glade'
self.builder = Gtk.Builder()
self.builder.add_from_file(gladefile)
main_win = self.builder.get_object("window1")
main_win.connect("delete-event", Gtk.main_quit)
button = self.builder.get_object('button')
button.connect('clicked', self.plot)
main_win.show_all()
def plot(self, widget):
window2 = self.builder.get_object("window2")
window2.connect("delete-event", Gtk.main_quit)
scrolledwindow = self.builder.get_object("scrolledwindow")
# ----- Start of Matplotlib specific code -----
figure = Figure(figsize=(8, 6), dpi=71)
axis = figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01)
s = np.sin(2*np.pi*t)
axis.plot(t, s)
axis.set_xlabel('time [s]')
axis.set_ylabel('voltage [V]')
canvas = FigureCanvas(figure) # a Gtk.DrawingArea
canvas.set_size_request(800, 600)
scrolledwindow.add_with_viewport(canvas)
# ----- End of Matplotlib specific code -----
window2.show_all()
if __name__ == "__main__":
main = PlotApp()
Gtk.main()
The imports are from vext.gi, numpy and matplotlib python packages, the Glade version I use is 3.22.1 and my OS is Elementary Linux 5.1 Hera.
You need to change the calls:
.connect("delete-event", Gtk.main_quit)
So that they only call Gtk.main_quit if this is the last open window.
Replace that direct call to Gtk.main_quit with this:
def exit(window, event):
if not any(w.get_visible() for w in Gtk.Window.list_toplevels() if w is not window):
Gtk.main_quit()
This makes it so that closing a window will only exit the application if it is the last visible window.
If you only do:
window.hide()
return True
in your destroy-event handler, you will not have the application close ever.
Combining that which I didn't see as an issue as I couldn't run your program with the code I suggest would result in main_quit being called when all of the windows are closed.
After some research (Reopen window throws Gtk-CRITICAL **: gtk_widget_get_window: assertion 'GTK_IS_WIDGET (widget)' failed) and some experimenting I concluded that the second-popup window, which is triggered by the button of my main window, should not be closed with the event function Gtk.main_quit(), but it should be hidden using the event function hide(). As a result the first-main window will remain open.
However, since the matplotlib plot is plotted on a FigureCanvas which is contained on the child widget (scrollbar window) of my second-popup window, then it is required the delete-event of popup-window to destroy the scrollbar window in order to be able to replot and avoid errors about existing child widgets.
So in the code of my original question the following modifications add the required functionality:
[1] Delete the scrollbar window widget from glade file
[2] Add a scrollbar window on top of popup window:
scrolledwindow = Gtk.ScrolledWindow()
#----- Start of Matplotlib specific code -----
figure = Figure(figsize=(8, 6), dpi=71)
axis = figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01)
s = np.sin(2*np.pi*t)
axis.plot(t, s)
axis.set_xlabel('time [s]')
axis.set_ylabel('voltage [V]')
canvas = FigureCanvas(figure) # a Gtk.DrawingArea
canvas.set_size_request(800, 600)
scrolledwindow.add_with_viewport(canvas)
#scrolledwindow.add(canvas)
# ----- End of Matplotlib specific code -----
window2.add(scrolledwindow)
window2.show_all()
[3] Change the event function of popup window:
window2.connect("delete-event", self.destroy)
where the destroy function is defined as:
def destroy(self, widget, event):
scrolledwindow = widget.get_child()
scrolledwindow.destroy()
widget.hide()
return True
Then no matter how many times I close the second window and press the button in main window, the plot is plotted on the (hidden) second window.
Related
I use tkinter and CTK:
I have created a page for login and I want to stop or use this page when the user is logged in, I want to show a new window and I want to resume the window when I want? How can I do that, I didn't know how to make it
I'll bite. Here's an example application that opens a second window when the user clicks a button on the main window, and disables interaction with the main window until the second window is closed.
Some configuration has been omitted for brevity, and I'm not using CTk here because I don't know how you've implemented it in your specific application - however, it should be easy enough to modify this example to work with CTk.
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.open_button = ttk.Button(
self,
text='Open 2nd Window',
command=self.modal
)
self.open_button.pack()
def modal(self):
self.window = tk.Toplevel(self) # create new window
# bind a handler for when the user closes this window
self.window.protocol('WM_DELETE_WINDOW', self.on_close)
# disable interaction with the main (root) window
self.attributes('-disabled', True)
self.close_button = ttk.Button(
self.window,
text='Close Modal',
command=self.on_close
)
self.close_button.pack()
def on_close(self):
# re-enable interaction the root window
self.attributes('-disabled', False)
# close the modal window
self.window.destroy()
if __name__ == '__main__':
app = App()
app.mailoop() # run the app
In the future:
Provide code that shows you made a good-faith effort to solve the problem on your own
Don't post the same question multiple times within hours - if you need to make changes, edit the original question
If you mean you want to open up another window to do something before going back to the original window, you should consider using message box. Here is a link that goes over the types of messageboxes: https://docs.python.org/3/library/tkinter.messagebox.html.
I'm currently developing a PyGObject app and I'm having issues selecting specific children in a Gtk+ FlowBox. Even after selecting the FlowBox selection mode (SINGLE) populating the FlowBox and writing code to select a specific child, the first child is always selected.
#!/usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class App(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="App")
flowbox = Gtk.FlowBox()
flowbox.set_valign(Gtk.Align.START)
flowbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
# Drawing 3 squares
flowbox.add(self.drawing_area())
flowbox.add(self.drawing_area())
flowbox.add(self.drawing_area())
child = flowbox.get_child_at_index(2)
flowbox.select_child(child)
flowbox.queue_draw()
self.add(flowbox)
def drawing_area(self):
preview = Gtk.DrawingArea()
preview.connect("draw", self.draw_square)
preview.set_size_request(150, 150)
return preview
def draw_square(self, widget, cr):
cr.scale(150, 150)
style_context = widget.get_style_context()
color = style_context.get_color(Gtk.StateFlags.NORMAL)
cr.set_source_rgba(*color)
cr.rectangle(0, 0, 1, 1)
cr.fill()
window = App()
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
Even though I choose to select the child at index 2, the app only ever shows the first child being selected:
Screenshot of above code running
The strange part is that when I check to see which child is selected using the following code (placed before the "self.add(flowbox)" line), the Terminal displays that the child I specified to be selected (at index 2) is the only selected child, even though the window only shows the first child being selected:
for child in flowbox.get_selected_children():
print child.get_index()
I think you have located a bug in GTK, it seems that something in show_all is messing up. My first guess was that it was caused by the fact that the FlowBox wasn't realized so I changed your code to use the show signal (realize but show is emitted later) and checked whether it still happend. Sadly it was..
So I got the feeling that something else was off so just a quick test added self.show() right after Gtk.Window.__init__ this made the selection work but made the Flowbox wider than needed (probably because of the default width of a empty window). So I added the self.show() in the listener and this actually solved the issue.
The complete code is as follows, but as it is a dirty workaround you should still report this bug.
#!/usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class App(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="App")
self.flowbox = Gtk.FlowBox()
self.flowbox.set_valign(Gtk.Align.START)
self.flowbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
# Drawing 3 squares
self.flowbox.add(self.drawing_area())
self.flowbox.add(self.drawing_area())
self.flowbox.add(self.drawing_area())
self.flowbox.connect("show", self.on_realize)
self.add(self.flowbox)
def on_realize(self, flowbox):
# The creative workaround/hack
self.show()
child = self.flowbox.get_child_at_index(2)
self.flowbox.select_child(child)
def drawing_area(self):
preview = Gtk.DrawingArea()
preview.connect("draw", self.draw_square)
preview.set_size_request(150, 150)
return preview
def draw_square(self, widget, cr):
cr.scale(150, 150)
style_context = widget.get_style_context()
color = style_context.get_color(Gtk.StateFlags.NORMAL)
cr.set_source_rgba(*color)
cr.rectangle(0, 0, 1, 1)
cr.fill()
window = App()
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
Is it possible to automatically activate the main window of a tkinter app? I am using Yosemite on a Mac. When the window comes up, the title bar is grayed out, and I have to click on the window before it will respond to events. The Tk manual says that event generate, "Generates a window event and arranges for it to be processed just as if it had come from the window system." I tried generating a <Button-1> event, but it had no effect. The manual goes on to say, "Certain events, such as key events, require that the window has focus to receive the event properly." I tried focus_force, but it didn't work either.
Is it possible to do what I want? Is this a Mac peculiarity? In the code below, the text changes as the mouse cursor enters and leaves the label, but the app is unresponsive until you click on the window.
import tkinter as tk
root = tk.Tk()
def visit(event):
kilroy['text'] = 'Kilroy was here.'
def gone(event):
kilroy['text'] = 'Kilroy has left the building'
def startup():
root.focus_force()
root.event_generate('<Button-1>')
frame = tk.Frame(root, width=500,height=100)
kilroy = tk.Label(frame, text="Kilroy hasn't been here.", width = 50)
kilroy.grid(row=0,column=0)
frame.grid(row=0,column=0)
kilroy.grid_propagate(0)
frame.grid_propagate(0)
kilroy.bind('<Enter>', visit)
kilroy.bind('<Leave>', gone)
root.after(100,startup)
root.mainloop()
I am building a GUI with PyQt4. At the moment I have a OpenGLWidget() that displays geometry at every N-th time step from Solver() that is in a different thread. I added new graph window - GraphWidget() to plot "some interesting" data. In Solver() I use a signal to refresh Open GL widget - self.updateGL(). I have connected the existing signal to also redraw FigureCanvasQTAgg but nothing is drawn.
Here is the class SimulationControlWidget:
class SimulationControlWidget(self):
self.OpenGLWidget = OpenGLWidget() #opengl widget
self.GraphWidget = GraphWidget() #plot widget with matplotib
# solver in new thread
self.solver = Solver()
self._solver_thread = QThread()
self._solver_thread.start()
self.solver.moveToThread(self._solver_thread)
# signals
#this signal is OK
OKself.solver.solveODE.repaintGL_signal.signal_repaintGL.connect(self.OpenGLWidget.updateGL)
#this signal is not OK
self.solver.solveODE.repaintGL_signal.signal_repaintGL.connect(self.GraphWidget.plot_graph)
Here is the class GraphWidget:
class GraphWidget(QtGui.QWidget):
def __init__(self, MBD_system=[], parent=None, flags=0):
super(GraphWidget, self).__init__(parent)
self.fig = Figure((8.0, 4.0), dpi=100)
self.canvas = FigureCanvas(self.fig)
self.axes = self.fig.add_subplot(111)
# added button to manually execute function plot_graph()
self.plot_button = QtGui.QPushButton("&Show")
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.canvas)
self.layout.addWidget(self.plot_button)
self.canvas.draw()
# signal to plot data with button self.plot_button
self.plot_button.clicked.connect(self.plot_graph)
def plot_graph(self):
print 'plotting graph!'
self.axes.cla()
self.axes.plot(np.random.rand(10, 2))
self.canvas.draw()
The problem I have is: if I manually click on the button self.plot_button the function plot_graph() is executed (random data is plotted correctly and text plotting_graph! is printed in eclipse pydev console). But if I try to execute function plot_graph() with signal only text plotting_graph! is printed in console and no data is plotted. I have also noticed if I use signal, the function plot_graph() is executing (due to signal) after the simulation has stopped. What should be the solution? I can also put the link to the entire code if it will be more useful. I think I will have to synchronize this...but how?
When using matplotlib:
from matplotlib import pyplot as plt
figure = plt.figure()
ax = figure.add_subplot(111)
ax.plot(x,y)
figure.show() # figure is shown in GUI
# How can I view the figure again after I closed the GUI window?
figure.show() # Exception in Tkinter callback... TclError: this isn't a Tk application
figure.show() # nothing happened
So my questions are:
How can I get the previous plot back if I have called figure.show()?
Is there a more convenient alternative to figure.add_suplot(111) if I have multiple figures and thus from pylab import *; plot(..); show() seems not a solution I'm looking for.
And what I eagerly want is
showfunc(stuff) # or
stuff.showfunc()
where stuff is an object containing all the plots arranged in one picture, and showfunc is STATELESS(I mean, each time I call it, I behaves as if it's first time called). Is this possible when working with matplotlib?
I can't find a satisfactory answer, so I handle this problem by writing a custom Figure class extending matplotlib.figure.Figure and providing a new show() method, which create a gtk.Window object each time called.
import gtk
import sys
import os
import threading
from matplotlib.figure import Figure as MPLFigure
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NaviToolbar
class ThreadFigure(threading.Thread):
def __init__(self, figure, count):
threading.Thread.__init__(self)
self.figure = figure
self.count = count
def run(self):
window = gtk.Window()
# window.connect('destroy', gtk.main_quit)
window.set_default_size(640, 480)
window.set_icon_from_file(...) # provide an icon if you care about the looks
window.set_title('MPL Figure #{}'.format(self.count))
window.set_wmclass('MPL Figure', 'MPL Figure')
vbox = gtk.VBox()
window.add(vbox)
canvas = FigureCanvas(self.figure)
vbox.pack_start(canvas)
toolbar = NaviToolbar(canvas, window)
vbox.pack_start(toolbar, expand = False, fill = False)
window.show_all()
# gtk.main() ... should not be called, otherwise BLOCKING
class Figure(MPLFigure):
display_count = 0
def show(self):
Figure.display_count += 1
thrfig = ThreadFigure(self, Figure.display_count)
thrfig.start()
Make this file as start file of IPython. And
figure = Figure()
ax = figure.add_subplot(211)
... (same story as using standard `matplotlib.pyplot` )
figure.show()
# window closed accidentally or intentionally...
figure.show()
# as if `.show()` is never called
Works! I never touched GUI programming, and don't know if this would have any side-effect. Comment freely if you think something should be done that way.