Python 34 filling width of scrollable frame in tkinter - user-interface

Here is my code:
from tkinter import *
import textwrap
GUI = Tk()
note_frame = Frame(GUI)
note_frame.grid(row=0, column=0)
note_scrollbar = Scrollbar(note_frame)
note_scrollbar.pack(side=RIGHT, fill=Y)
note_container = Canvas(note_frame, yscrollcommand = note_scrollbar.set)
note_container.pack()
note_scrollbar.config(command=note_container.yview)
def configure_note_container(event):
note_container.config(scrollregion=note_container.bbox("all"),
width=200, height=200)
note_list = Frame(note_container, width=200, height=200)
note_container.create_window((0,0), window=note_list, anchor='nw')
note_list.bind("<Configure>", configure_note_container)
for x in range(100):
exec(textwrap.dedent(
"""
label_{0} = Label(note_list, text='{0}', bg="white")
label_{0}.grid(column=0, row={0})
""".format(x)))
GUI.mainloop()
I am trying to get the labels to stretch out to fill the X width of the grid cell within the frame (which is within a canvas so that I can use a scrollbar), but I don't know how to do it - when the note_container gets configured, it seems to cancel out any adjustment of size. grid_propagate doesn't seem to work either.
Any help is appreciated. thanks.

Related

window.read doesn't return events after creating any window in the matplotlib' toolbar

The code is based on: https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
One custom button is added to the toolbar and if we call, ex. sg.popup from callback function for this button main loop become broken - no events are returned from any button (Plot and Exit in the example).
import PySimpleGUI as sg
import numpy as np
import os
import sys
import matplotlib.backends
import base64
"""
Embedding the Matplotlib toolbar into your application
Based on:
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
"""
# ------------------------------- This is to include a matplotlib figure in a Tkinter canvas
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
if os.path.isfile("axsminmax.png") == False:
toolbarpng1 = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAABsklEQVRYhe3Xz2oUQRAG8F8mvoH4CuJbeDagQvBoLtldl+BJIko04j98BiX+IycPXox61zyFkKsevedkxkPXkHHipHd2nQ2KHxTM1lbX9011d/U0s2GE4Yw5psYtlGF3502+GcQ/wsoQNBfcDsLX2MVnPAvfRt/k4yDaRoFPYUX4Sqx0SXiqo4CveIyHOKj5D7CKb/jeMedMqCowNYo/JOTvFdB1DTTxRlp4/xZWcKGHvMtYywXdlEr6JBNX4BreY0c6E3Lr6X7kftAWUPX2bSxmyD9G7F5YiQ8ZEQvYahNRb6+5NxlFbL2ca+EbZMYWeFEXsRAJt6RDZdevHa7C24ghlf0czjZi9vAFl+P3GFdaRJyXqnx90j4w6VYrW57bsF89bMSAl/JTMNQ+BauZsYU0zSXWm39OKqKQFlxzEe5IU9qGRYcn5hHypohHxyQSREO8CxtkyElb+1jyCgNczAVNgSVc7SHvyWIsdcQTw/8PkrkLWJK20+8Oq0Lavpf6FHAad/CqMbbAc9zDmY45O+OGoxeTp+Hb7Ju8QvXtUL+a9X4ramLd4eV0bm/exChsavwEAXVwI8ngt8MAAAAASUVORK5CYII='
with open("axsminmax.png", "wb") as fh:
fh.write(base64.decodebytes(toolbarpng1))
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
if canvas.children:
for child in canvas.winfo_children():
child.destroy()
if canvas_toolbar.children:
for child in canvas_toolbar.winfo_children():
child.destroy()
figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
figure_canvas_agg.draw()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
def get_res_file_path(fname):
if hasattr(sys, "_MEIPASS"):
fpath = os.path.join(sys._MEIPASS, fname)
else:
basedir = os.path.abspath(os.getcwd())
fpath = basedir+'/'+fname
return fpath
def callback_func_P(NavigationToolbar2TK):
def wrapper():
print('Plot on min/max from toolbar')
sg.popup('This window blocks main loop.\nPlot, Exit and Alive? buttons not working after that.\nAnd Terminal window could be closed by x only!')
return wrapper
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
self.toolitems = NavigationToolbar2Tk.toolitems+((None, None, None, None),)
super(Toolbar, self).__init__(*args, **kwargs)
self._buttons["Plotmm"] = button = self._Button("Plotmm", get_res_file_path('axsminmax.png'), toggle=False, command=callback_func_P(self))
Tooltip = getattr(matplotlib.backends, '_backend_tk').ToolTip
Tooltip.createToolTip(button, "Plot data on min/max")
# ------------------------------- PySimpleGUI CODE
layout = [
[sg.T('Graph: y=sin(x)')],
[sg.B('Plot'), sg.B('Exit')],
[sg.T('Controls:')],
[sg.Canvas(key='controls_cv')],
[sg.T('Figure:')],
[sg.Column(
layout=[
[sg.Canvas(key='fig_cv',
# it's important that you set this size
size=(400 * 2, 400)
)]
],
background_color='#DAE0E6',
pad=(0, 0)
)],
[sg.B('Alive?')]
]
window = sg.Window('Graph with controls', layout)
while True:
event, values = window.read()
print(event, values)
if event in (sg.WIN_CLOSED, 'Exit'): # always, always give a way out!
break
elif event is 'Plot':
# ------------------------------- PASTE YOUR MATPLOTLIB CODE HERE
plt.figure(1)
fig = plt.gcf()
DPI = fig.get_dpi()
# ------------------------------- you have to play with this size to reduce the movement error when the mouse hovers over the figure, it's close to canvas size
fig.set_size_inches(404 * 2 / float(DPI), 404 / float(DPI))
# -------------------------------
x = np.linspace(0, 2 * np.pi)
y = np.sin(x)
plt.plot(x, y)
plt.title('y=sin(x)')
plt.xlabel('X')
plt.ylabel('Y')
plt.grid()
# ------------------------------- Instead of plt.show()
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig, window['controls_cv'].TKCanvas)
window.close()
If instead of sg.popup we'll create our own window the result will be same.
Steps to reproduce.
1.Start script.
2.Press button Plot.
3.Press most right toolbar' button (tooltip-'Plot data on min/max').
4.Press Plot or Exit buttons - nothing is happen.
Know nothing about the relation between function wrapper and tkinter.
My suggestion is to use method window.write_event_value to generate an event to main loop to do something about the GUI.
def callback_func_P(NavigationToolbar2TK):
def wrapper():
print('Plot on min/max from toolbar')
window.write_event_value('Popup', 'This window blocks main loop.\nPlot, Exit and Alive? buttons not working after that.\nAnd Terminal window could be closed by x only!')
return wrapper
and this in the main event loop
elif event == 'Popup':
sg.popup(values[event])

pyqtgrapgh: Image Item not in the center of GraphicsView

I am using pyqtgraph to visualate 2D arrays. Due to the fact that I have to refresh the visualated Image because of a real time application which I want to evaluate I am using GraphicsView(). Then I create a ImageItem and I want to add the Item to the created window. Unfortunately the image is only visible in the left upper corner of the window. I know that I can show the full image without GraphicsView but as I said I will need GraphicsView later to visualate the updated values in real time for an application.
Here is my current code:
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import struct
import sounddevice as sd
from scipy.fftpack import fft
import sys
import time
class Fenster(pg.GraphicsLayoutWidget): #objektorientiert arbeiten!!
def __init__(self): #constructor
super().__init__() #ruft den constructor von QWidget auf
self.initMe()
class Fenster(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
self.initMe()
def initMe(self):
self.win = pg.GraphicsView()
self.win.show()
self.plot_data = np.fromfunction(lambda i, j: (1+0.3*np.sin(i)) * (i)**2 + (j)**2, (100, 100))
self.img = pg.ImageItem(image=self.plot_data)
self.cm = pg.colormap.get('CET-L9')
self.bar = pg.ColorBarItem( values= (0, 20_000), colorMap=self.cm )
self.bar.setImageItem(self.img)
self.win.setCentralItem(self.img)
w = Fenster()
That is the output image:
How can I set that the image fullfill the whole window?
How can I do that the created ColorBarItem will be showed in the window? (optionally)
Thanks in advance.
I found the solution. You have to create a PlotItem. Then add the ImageItem to the PlotItem and set the PlotItem in the center of GraphicsView
self.win = pg.GraphicsView()
self.win.show()
self.p = pg.PlotItem()
self.win.setCentralItem(self.p)
self.plot_data = np.fromfunction(lambda i, j: (1+0.3*np.sin(i)) * (i)**2 + (j)**2, (100, 100))
self.img = pg.ImageItem(image=self.plot_data)
self.cm = pg.colormap.get('CET-L9')
self.bar = pg.ColorBarItem( values= (0, 20_000), colorMap=self.cm )
self.bar.setImageItem(self.img)
self.p.addItem(self.img)
I also figured out to update the plot (live plot). Leave a comment if I should share.

Display text on another process' screen (overlay)

I have a question, its more an OS-based one.
I'm playing a video game and I want to be able to put a textual timer ontop of the game's screen as if it was a part of the game itself.
Now, I can write a program in any language that displays a TextBox with a timer on the screen, but if I run it, the game's process (lets call it game.exe) "loses" its focus and I get my TextBox focused and interactive by the OS.
Is there any option to display that text "ontop" of the game.exe that comes from an entire different process? as if there were "layers" to the screen. Also, this text shouldn't be intractable, clickable or make the game.exe process lose its focus.
Here's a very simple example I drew:
Thanks a lot!
Solved this using a window trick with python and tkinter with some windows api stuff.
The trick is to create a transparent non-clickable window and keep it always on top.
I've basically combined this answer with a bunch of simpler stuff like removing window's border and set to auto fullscreen.
from tkinter import *
import time
import win32gui
import win32api
from win32api import GetSystemMetrics
# WIDTH = 500
# HEIGHT = 500
WIDTH = GetSystemMetrics(0)
HEIGHT = GetSystemMetrics(1)
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
# tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
tk.attributes('-fullscreen', True)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, highlightthickness=0)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
canvas.create_text(WIDTH/2,HEIGHT/2,fill="white",font="Arial 20", text="TEXT GOES HERE")
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))

Centering QLabel inside layout

I'm trying to center a QLabel showing a pixmap inside a QWidget both horizontally and vertically, but for some reason, this seems impossible. I have read many similar questions, and it seems they all comes down to specifying the alignment when adding the label to the layout. Well, I'm doing that and still it's aligning to the top left corner. Can someone please help me center my darn QLabel already? :)
main.py
import sys
from PyQt5.QtWidgets import QApplication
from mainwindow import MainWindow
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
mainwindow.py
from PyQt5.QtGui import QGuiApplication, QWheelEvent
from PyQt5.QtWidgets import QMainWindow
from imagewidget import ImageWidget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(QGuiApplication.primaryScreen().availableSize() * 3 / 5)
self.image_widget = ImageWidget()
self.setCentralWidget(self.image_widget)
def wheelEvent(self, event: QWheelEvent) -> None:
angleDelta = event.angleDelta().y()
if angleDelta >= 0:
self.image_widget.zoomIn()
else:
self.image_widget.zoomOut()
imagewidget.py
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QPalette
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QPushButton
class ImageWidget(QWidget):
def __init__(self):
super().__init__()
self.scale_factor = 1.0
self.label = QLabel()
self.label.setAlignment(Qt.AlignVCenter)
self.label.setPixmap(QPixmap("image.png")) # Loads local test image
self.label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.label.setScaledContents(True)
self.layout = QVBoxLayout()
self.layout.addWidget(self.label, Qt.AlignCenter) # Why does this not align the label to the center of the layout?
self.setLayout(self.layout)
def zoomIn(self):
self.scale_factor *= 1.1
self.resizeUsingScaleFactor()
def zoomOut(self):
self.scale_factor /= 1.1
self.resizeUsingScaleFactor()
def getImageSize(self):
return self.label.pixmap().size()
def resizeUsingScaleFactor(self):
self.label.resize(self.getImageSize() * self.scale_factor)
When you resize a widget it doesn't adjust its position, it just resize it. All widgets set their geometry based on the origin point (the top left corner), if you use resize the origin point will always remain the same.
Since you are using a layout, you should leave the positioning to the layout (which you are also preventing since you're using the Ignore size policy, which is a problem in these cases. Also note that you are using the alignment as the second argument for addWidget, but its signature is addWidget(widget, stretch=0, alignment=Qt.Alignment()), so you should use the correct keyword.
The solution is to use setFixedSize instead, which will ensure that the layout takes care of the correct alignment once it has been notified about the new fixed size (which does not happen when you use resize).
class ImageWidget(QWidget):
def __init__(self):
super().__init__()
self.scale_factor = 1.0
self.label = QLabel()
# no need for this
# self.label.setAlignment(Qt.AlignCenter)
# don't do this
# self.label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.label.setPixmap(QPixmap("image.png")) # Loads local test image
self.label.setScaledContents(True)
self.layout = QVBoxLayout()
self.layout.addWidget(self.label, alignment=Qt.AlignCenter)
self.setLayout(self.layout)
# ...
def resizeUsingScaleFactor(self):
self.label.setFixedSize(self.getImageSize() * self.scale_factor)

Matplotlib embedded in wxPython: TextCtrl in Navigation toolbar not working on macos

I'm doing a simple embedded graph with Matplotlib APIs (2.2.2) in wxPython (Phoenix 4.0.1) and Python 3.6.4. I have subclassed the WXAgg Navigation toolbar so I can remove the "configure subplots" tool and this is working fine.
In addition, I have added a read-only TextCtrl into my subclassed toolbar to show mouse coordinates (just like it appears in the pyplot state-based version of matplotlib). I've implemented a simple handler for the mouse move events per the Matplotlib docs and this is all working fine on Windows 10.
However, this code does not fully work on macOS (10.13.4 High Sierra). The graph displays just fine, the toolbar displays fine, the toolbar buttons work fine, but I don't get any display of my TextCtrl with the mouse coordinates in the toolbar (or even the initial value as set when I create the TextCtrl).
Can anyone shed light on why the TextCtrl in the Matplotlib toolbar doesn't work on the mac? Is there a way to do this on the mac? And if this is simply not possible, what are my alternatives for showing the mouse coordinates elsewhere in my Matplotlib canvas?
Here's my sample code:
import wx
from matplotlib.figure import Figure
from matplotlib import gridspec
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavigationToolbar
class MyToolbar(NavigationToolbar):
def __init__(self, plotCanvas):
# create the default toolbar
NavigationToolbar.__init__(self, plotCanvas)
# Add a control to display mouse coordinates
self.info = wx.TextCtrl(self, -1, value = 'Coordinates', size = (100,-1),
style = wx.TE_READONLY | wx.BORDER_NONE)
self.AddStretchableSpace()
self.AddControl(self.info)
# Remove configure subplots
SubplotsPosition = 6
self.DeleteToolByPos(SubplotsPosition)
self.Realize()
class Graph(wx.Frame):
def __init__(self, parent, title='Coordinates Test'):
super().__init__(parent, title=title)
self.SetSize((900, 500))
# A simple embedded matplotlib graph
self.fig = Figure(figsize = (8.2,4.2), facecolor = 'gainsboro')
self.canvas = FigureCanvas(self, -1, self.fig)
gs = gridspec.GridSpec(2, 1, left = .12, right = .9, bottom = 0.05, top = .9, height_ratios = [10, 1], hspace = 0.35)
ax = self.fig.add_subplot(gs[0])
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
ax.plot(t, s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='About as simple as it gets, folks')
ax.grid()
ax.set_navigate(True)
# Get a toolbar instance
self.toolbar = MyToolbar(self.canvas)
self.toolbar.Realize()
# Connect to matplotlib for mouse movement events
self.canvas.mpl_connect('motion_notify_event', self.onMotion)
self.toolbar.update()
# Layout the frame
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.EXPAND)
self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
self.SetSizer(self.sizer)
def onMotion(self, event):
if event.inaxes:
xdata = event.xdata
ydata = event.ydata
self.toolbar.info.ChangeValue(f'x = {xdata:.1f}, y = {ydata:.1f}')
else:
self.toolbar.info.ChangeValue('')
class MyFrame(wx.Frame):
def __init__(self, parent, title=""):
super().__init__(parent, title=title)
self.SetSize((800, 480))
self.graph = Graph(self)
self.graph.Show()
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, title='Main Frame')
self.frame.Show()
return True
if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
I realize this is late, but I think that the simplest solution is to not subclass NavigationToolbar at all, but just to add a TextCtrl of your own.
That is, getting rid of your MyToolbar altogether and modifying your code to be
# Get a toolbar instance
self.toolbar = NavigationToolbar(self.canvas)
self.info = wx.TextCtrl(self, -1, value = 'Coordinates', size = (100,-1),
style = wx.TE_READONLY | wx.BORDER_NONE)
self.canvas.mpl_connect('motion_notify_event', self.onMotion)
self.toolbar.update()
# Layout the frame
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.EXPAND)
bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
bottom_sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
bottom_sizer.Add(self.info, 1, wx.LEFT | wx.EXPAND)
self.sizer.Add(bottom_sizer, 0, wx.LEFT | wx.EXPAND)
self.SetSizer(self.sizer)
def onMotion(self, event):
if event.inaxes is not None:
xdata = event.xdata
ydata = event.ydata
self.info.ChangeValue(f'x = {xdata:.1f}, y = {ydata:.1f}')
else:
self.info.ChangeValue('')
will give TextCtrl that does display the motion events.

Resources