Tkinter/matplotlib multiple active windows on osx - python

I have a script as follows that executes on my windows machine
import matplotlib.pyplot as plt
import numpy as np
import tkinter
class main(tkinter.Frame): #main window
def __init__(self, root): # initialise
tkinter.Frame.__init__(self)
self.root = root
tkinter.Button(self, text='New spots', command=self.newSpots).grid()
def newSpots(self):
x = np.random.rand(10)
y = np.random.rand(10)
plt.scatter(x,y)
plt.show()
if __name__=='__main__':
root = tkinter.Tk()
app = main(root).grid()
root.mainloop()
When running on windows, it opens a window with a simple button, and clicking this button opens a matplotlib viewer with 10 dots plotted in random positions. Each subsequent press of the button adds a further ten dots.
Executing this code on a mac produces the same initial window, and the first press of the button generates the plot and opens the viewer as expected. However, it then becomes impossible to interact with the original window (only the controls on the viewer work) until the viewer window is closed. How do I make the behaviour on the mac mirror that on the windows machine?

I found a solution to this issue -- it seems matplotlib defaults to the TkAgg backend on Windows (I'm unsure whether this is a general Windows thing, or specific to whatever particular install is on the machine).
Adding the following lines to the top of the script forces the TkAgg backend and leads to the same behaviour on both machines.
import matplotlib
matplotlib.use("TkAgg")

Related

Why does my tkinter window and plot appear differently on different monitors, as well as before/after pyinstaller?

I'm developing a program with a tkinter GUI that has an embedded matplotlib bar chart. The program itself is sturdy, but the GUI gives me some weird issues.
On the PC in my office (windows 10, 1920x1080 monitor), running the program in sublime text shows a perfectly normal GUI. I then built the program into a single executable using pyinstaller, and running this also gives me perfectly normal results.
However, if I use a separate PC in my office connected to the same network (also windows 10, 3840x2160 monitor), that's where I experience issues. Running it before pyinstaller, the window appears much smaller than my PC and the widgets are not scaled down.
Running it after pyinstaller scales the widgets... but to a teeny tiny window.
Below is my minimal reproducible example. Please let me know what I may be doing wrong. I've successfully built programs like this before, so I'm not sure why this one is giving such a fuss. Thanks in advance.
import tkinter as tk
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib import animation as animation, pyplot as plt, cm
from tkinter import *
# window settings (size, color...)
root = tk.Tk()
root.geometry('678x600')
# settings for the plot size and layout
plt.rcParams["figure.dpi"] = 96 #figure_dpi
plt.rcParams["figure.figsize"] = [6.75, 4.0]
fig = plt.figure() # make a figure for our plot
# text box and label for user to enter desired pulse width
pulse_txt = tk.Text(root, height=1, width=6, font=("Arial", 14))
pulse_lbl = tk.Label(root, text="label label hi", bg="#EEEDEB")
# button to stop counting and export file with counts
end_count_btn = tk.Button(root, text="button", width=10)
# Link the matplotlib figure to our main window and specify its location
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().place(relx = 0.02, rely = 0.25) ######
pulse_lbl.place(relx = 0.438, rely = 0.03)
pulse_txt.place(relx = 0.451, rely = 0.07)
end_count_btn.place(relx = 0.445, rely = 0.15)
ax = fig.add_subplot(111)
# create the plot and specify some starting axis info and the title
bars = plt.bar(np.linspace(1,1000,1000), np.zeros(1000), facecolor='#990000', width=1)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title("plot")
ax.set_xlim([0,500])
# create the nifty toolbar below the plot
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
root.mainloop()
exit(0)
I was able to solve my issue outside of code. The problem was that python.exe and the .exe file that I build with pyinstaller were both using high DPI scaling behavior. Because of this, running the program on the PC with higher DPI resulted in all the widgets being shrunk by under-the-hood functionality.
To stop this from happening, you must do the following with python.exe as well as the .exe built by pyinstaller:
Right click on the .exe in explorer
Select Properties
Select the Compatibility tab
Select "Change high DPI settings"
Check the "Override high DPI scaling behavior" box
Click "OK", "Apply", "OK"
Running the executable after taking these actions results in a correctly sized application window.
Extra thanks to #Bryan Oakley for recommending I use the pack() or grid() methods instead of place() for arranging my widgets. I went with grid() which I found to be the easiest. Only caveat being, the toolbar widget has a pack() statement built in, so in order to get it to play nice with the rest of my widgets using grid(), I had to put the toolbar inside of a frame and use grid() to place that frame.

Matplotlib: how to make the background transparent on Windows

I need the whole plot window to be transparent so that a chrome window, for example, on my desktop could be seen through the plot, so that I can add points to it while seeing what's behind it.
https://stackoverflow.com/a/45505906/13650485
The answer I've listed above is EXACTLY what I want to do, except my interactive system doesn't work with TK. I'd like to use Qt5Agg. When I run the code above, the system won't accept it -- it says QT5 is currently running. If I run it without QT already loaded, it creates a blank transparent window (yay!) but if I move it or click on the icon it turns opaque black without any plot. If I change tk to Qt5 it complains on lift. If I remove the "win" code, it has no transparency(obviously). I've tried adding everything I can think of to make the canvas transparent and I can change the color but not make it transparent.
import matplotlib
# make sure Tk backend is used
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
# create a figure and some subplots
fig, ax = plt.subplots(figsize=(4,2))
ax.plot([2,3,5,1])
fig.tight_layout()
win = plt.gcf().canvas.manager.window
win.lift()
win.attributes("-topmost", True)
win.attributes("-transparentcolor", "white")
plt.show()
When I made the changes suggested by: eyllanesc
I found within a vanilla Spyder 4.1.3 | Python 3.7.7 64-bit | Qt 5.9.6 | PyQt5 5.9.2 | Windows 10
In order to import QtCore I had to first
conda install pyqt
not enough, so then conda install pyqt5
and also conda update --all
When I did that, the code ran without errors. This is a better first result!, but I still only get the frozen mpl.fig window. This time, however, it is white. . . The console returns, but the mpl window hangs. Run again, a new frozen window. Restart and run again: same result.
I hope that this is a simple error; please teach this newby.
#eyllanesc
Revised: Python screen tracing application – needs a mostly transparent plot window.
I need the whole plot window to be transparent so that a chrome window, for example, on my desktop could be seen through the plot, so that I can add plot (x, y) points to it while seeing what's behind it.
Adding the command win.setWindowFlags(QtCore.Qt.FramelessWindowHint) did indeed make the window transparent, but it made the tool bar transparent, got rid of the title bar, and removed the ability to move or resize the window. It also made it so that the graph area was not sensitive to the mouse unless I was over the line. I added the facecolor attribute to the subplots command so I could see what was going on. As long as I put a non-zero value for either the fig-alpha or the ax-alpha, the graph is sensitive to the mouse over the whole area.
I need to be able to move and resize the window and would like to have the toolbar be opaque or at least sensitive to the mouse over the whole toolbar. Can you help with this? Thanks for past help!
## Python Code Fragment by Helen for Windows 10
## to test sequence creating plot with transparent
## background (to be used to trace and record xy pairs)
from PyQt5 import QtCore
import matplotlib
matplotlib.use("Qt5Agg") #define backend, must be before pyplot is imported
import matplotlib.pyplot as plt
# create a figure and a subplot
fig,ax = plt.subplots(figsize=(4, 2),facecolor=(1.,1.,0.,0.1)) #facecolor of figure
fig.patch.set_alpha(0.1)
ax.patch.set_alpha(0.1)
# plot some fixed points
ax.plot([2, 3, 5, 1])
fig.tight_layout()
#make window transparent to the desktop
win = plt.gcf().canvas.manager.window
win.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
win.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
win.setStyleSheet("background:transparent")
win.setWindowFlags(QtCore.Qt.FramelessWindowHint)
win.setWindowTitle("My App")
plt.show()
You have to use the Qt flags, tested on Linux:
from PyQt5 import QtCore
import matplotlib
# make sure Tk backend is used
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
# create a figure and some subplots
fig, ax = plt.subplots(figsize=(4, 2))
fig.patch.set_alpha(0.0)
ax.patch.set_alpha(0.0)
ax.plot([2, 3, 5, 1])
fig.tight_layout()
win = plt.gcf().canvas.manager.window
win.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
win.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
win.setStyleSheet("background:transparent")
plt.show()

How to close a running windows OS program using a button?

I'm making a simple GUI using Python 3.7.3 and tkinter to open and close windows applications. I'm not able to find a way to close a running program using an onscreen button. I need the 'close' button to do something else as well, hence the simply using 'x' button (which is next to the minimize and maximize) won't work for my case.
from tkinter import *
import os, subprocess
root = Tk()
root.geometry("300x300")
def OpenCalc():
app1 = os.startfile("C:\Windows\System32\calc.exe")
def CloseCalc():
os.close(app1)
# or
# os.closefile("C:\Windows\System32\calc.exe")
b1=Button(root, text="Open Calc", command=OpenCalc).pack()
b2=Button(root, text="Close Calc", command=CloseCalc).pack()
root.mainloop()

Tkinter window not filling screen in zoomed state on secondary display

I'm building a small python 3 tkinter program on windows (10). From a main window (root) I'm creating a secondary window (wdowViewer). I want it to be full screen (zoomed) on my secondary display. The code below works on my main setup with two identical screens. If I however take my laptop out of the dock and connect it to (any) external display, the new window only fills about 2/3 of the secondary display.
Two things to note:
- The laptop and external monitor have same resolution.
- The window is appropriately zoomed when overrideredirect is set to 0.
mon_primary_width = str(app.root.winfo_screenwidth()) # width of primary screen
self.wdowViewer = Toplevel(app.root) # create new window
self.wdowViewer.geometry('10x10+' + mon_primary_width + '+0') # move it to the secondary screen
self.wdowViewer.wm_state('zoomed') # full screen
self.wdowViewer.overrideredirect(1) # remove tool bar
app.root.update_idletasks() # Apply changes
After two days of experimenting I finally found a fix.
Consider the following example: Making a toplevel zoomed, works fine on any secondary display when using the following code:
from tkinter import *
# Make tkinter window
root = Tk()
sw = str(root.winfo_screenwidth())
Label(root, text="Hello Main Display").pack()
# Make a new toplevel
w = Toplevel(root)
w.geometry("0x0+" + sw + "+0")
w.state('zoomed')
w.overrideredirect(1)
Label(w, text='Hello Secondary Display').pack()
root.mainloop()
However, in my code I'm making a new Toplevel after running the mainloop command. Then, the issue arises. A code example with the issue:
from tkinter import *
# New Tkinter
root = Tk()
sw = str(root.winfo_screenwidth())
# Function for new toplevel
def new_wdow():
w = Toplevel(root)
w.geometry("0x0+" + sw + "+0")
w.state('zoomed')
w.overrideredirect(1)
Label(w, text='Hello Secondary Display').pack()
# Make button in main window
Button(root, text="Hello", command=new_wdow).pack()
root.mainloop()
Problem: The bug is only present if the DPI scaling in Windows is not set to 100%. Hence, there seems to be a bug in how tkinter handles the DPI scaling subsequent to running mainloop. It does not scale the window properly, but Windows is treating it as if it does.
Fix: Tell Windows that the app takes care of DPI scaling all by itself, and to not resize the window. This is achievable using ctypes. Put the following code early in your python script:
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness(2)
Hopefully others will find the solution helpful. Hopefully, someone may explain more of what is going on here!

Change icon in a Matplotlib figure window

Is it possible to change the icon of a Matplotlibe figure window? My application has a button that opens a Figure window with a graph (created with Matplotlib). I managed to modify the application icon, but the figure window still has the 'Tk' icon, typical of Tkinter.
I solved it in this way:
BEFORE I press the button that creates the figure with imshow() and show(), I initialize the figure in this way:
plt.Figure()
thismanager = get_current_fig_manager()
thismanager.window.wm_iconbitmap("icon.ico")
so when I press show() the window has the icon I want.
For me the previous answer did not work, rather the following was required:
from Tkinter import PhotoImage
import matplotlib
thismanager = matplotlib.pyplot.get_current_fig_manager()
img = PhotoImage(file='filename.ppm')
thismanager.window.tk.call('wm', 'iconphoto', thismanager.window._w, img)
Just adding this here, now that the Qt5Agg backend has made it's way into the mainstream. It's similar (pretty much the same) to the Qt4Agg backend as outlined by Sijie Chen's answer.
import os
import matplotlib.pyplot as plt
from PyQt5 import QtGui
# Whatever string that leads to the directory of the icon including its name
PATH_TO_ICON = os.path.dirname(__file__) + '/static/icons/icon.ico'
plt.get_current_fig_manager().window.setWindowIcon(QtGui.QIcon(PATH_TO_ICON))
If you are using Qt4Agg backend, the following code may help you:
thismanager = plt.get_current_fig_manager()
from PyQt4 import QtGui
thismanager.window.setWindowIcon(QtGui.QIcon((os.path.join('res','shepherd.png'))))
I found that under OS X with PyQT5, doing plt.get_current_fig_manager().window.setWindowIcon() has no effect. To get the dock icon to change you have to call setWindowIcon() on the QApplication instance, not on the window.
What worked for me is:
QApplication.instance().setWindowIcon(QtGui.QIcon(icon_path))
Do mind that QApplication.instance() will be None until you have actually created a figure, so do that first.

Categories