Pass arrays between Python classes [duplicate] - python

This question already has answers here:
How to access variables from different classes in tkinter?
(2 answers)
Closed 6 years ago.
I have data in x and y arrays that I want to pass between two Python classes. When button is clicked I run command=lambda: controller.show_frame(PlotPage) to switch from SelectPage (which chooses data) to PlotPage (which plots x and y). I want x and y saved before the page switch or within the button lambda. Is saving the arrays as global variables the best way to pass the data to PlotPage, or is there a more convenient way to include these arrays in the button lambda function?
# possible global variables
global x = [stuff x]
global y = [stuff y]
class SelectPage(tk.Frame):
def __init__(self,parent,controller):
button = tk.Button(self,text="Plot",
command=lambda: controller.show_frame(PlotPage),
[some_lambda_here]) # Possible lambda addition
class PlotPage(tk.Frame):
def __init__(self,parent,controller):
[Tkinter plot intialization stuff]
plotData(x,y) # plotData creates the plot
Controller Class:
class Project:
def __init__(self, *args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
container = tk.Frame(self)
container.pack(side="top",fill="both",expand=True)
container.grid_rowconfigure(0,weight=1)
container.grid_columnconfigure(0,weight=1)
self.frames = {}
for F in (SelectPage, PlotPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0,column = 0, sticky = "nsew")
self.show_frame(StartPage)
def show_frame(self, container):
frame = self.frames[container]
frame.tkraise()

For communication between components, you should have a look at the Observer design pattern and MVC architecture. You could then structure the program along these lines (I'm skipping the Tk instructions here):
class Observable:
def __init__(self, signals):
# create signal map
def connect(self, signal, handler):
# append handler to the signal in the map
def emit(self, signal, *args, **kwargs):
# iterate over signal handlers for given signal
class Model(Observable):
def __init__(self):
super().__init__("changed")
self.x = []
self.y = []
def set_x(self, x):
self.x = x
self.emit("changed")
def append_x(self, value):
self.x.append(value)
self.emit("changed")
# same for y
class PlotView(SomeTKTClass):
def __init__(self, model):
self.model = model
model.connect("changed", lambda: self.plot(model.x, model.y))
def plot(self, x, y):
#some tk rendering here
# SelectPage and StartPage are defined somewhere.
class MainController(SomeTkClass):
def __init__(self):
# ...
self.model = Model()
self.startPage = StartPage() # I suppose
self.plotView = PlotView(self.model)
self.selectPage = SelectPage()
self.frames = {}
for view in {self.startPage, self.plotView, self.selectPage}:
self.frames[view.__class__] = view
# ...
self.show_frame(StartPage)
def show_frame(self, container):
frame = self.frames[container]
# ...
The implementation of the Observer pattern can be done in many ways. The one suggested here is simple. There are many ways to improve upon this rough sketch, but the idea is to let the observable model notify the view that its data has changed and can be redrawn in the plot.

Related

Creating diferent wimdows as parameters with its owm variables

class main():
def __init__(self):
self.root = tk.Tk()
# Icono
self.notebook=ttk.Notebook(self.root)
self.notebook.pack(fill='both',expand='yes')
# Crear Frames blancos
tab_FBR1=tk.Frame(self.notebook,bg='white') #
tab_FBR2=tk.Frame(self.notebook,bg='white') #
tab_FBR3=tk.Frame(self.notebook,bg='white') #
#ASIGNACIÓN PESTAÑAS FBR1,FBR2 Y FBR3
self.notebook.add(tab_FBR1,text='FBR1') #
self.notebook.add(tab_FBR2,text='FBR2') #
self.notebook.add(tab_FBR3,text='FBR3') #
# Configurations FBR1, FBR2 y FBR3
self.window_FBR(tab_FBR1)
self.window_FBR(tab_FBR2)
self.window_FBR(tab_FBR3)
I want to create 3 windows calling a method called def window_FBR, to create 3 windows with their own variables.
def window_FBR(self,tab):
self.rcolor=tk.IntVar(value=4)
tk.Radiobutton(tab, text="Red", variable=self.rcolor, value=1, command=self.color_rojo(tab),font=("arial",10),bg=('white')).place(x=10,y=70)
However is not working, have you guys have some ideas about how to manage the variables, in the method to create different variables each time I am calling the method?
many thanks
I want to create a GUI in Tkinter with 3 windows.
I have a problem because when the variables are created, they did not start with the default value for the self.rcolor=tk.IntVar(value=4)
My solution for multiple windows that have their own variables is to create an independent class that takes its own parameters, and has its own subclasses if necessary, you can pass one or several different parameters in each case, hope that helps.
from tkinter import *
class SubWin(Tk):
def __init__(self, string, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry('300x300')
label = Label(master=self, text=string)
label.pack()
class App(Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry('300x300')
sub1 = SubWin(string='Helooo1')
sub2 = SubWin(string='Helooo2')
if __name__ == '__main__':
app = App()
app.mainloop()

Passing Python objects as arguments to tkinter's Entry validatecommand

I'm using Tkinter's "validatecommand" function to validate inputs from a entry box. I want to pass my class object so that the validation-function can request information from the object. However, it seems that the validatecommand function turns everything I pass into strings. Because of this the validation-function now has __main__.foo object at 0x042981B0 but as string. How can I instead pass the original __main__.foo?
It currently looks like this (pseudo-code):
class foo(object):
def start(program):
self.stuff = 5 #stuff changes while the program is running
tkinter_stuff(program)
def tkinter_stuff(program):
Entry = tkinter.Entry(validatecommand = (window.register(validate_entry), '%P', program))
def validate_entry(entry, program): #checks if current stuff + the amount of staff that would be added over this entry box is <= 20
if int(entry) + program.get_stuff() <= 20:
return True
return False
program = foo() #there are other classes that create their own program and overwrite the one the entry uses, so I can't rely on this one
program.start(program)
actual code:
import tkinter
class foo(object):
def __init__(self):
self.stuff = 5 #stuff changes while the program is running
def start(self, program):
tkinter_stuff(program)
def get_stuff(self):
return self.stuff
def tkinter_stuff(program):
window = tkinter.Tk(className = 'window')
window.geometry('50x50')
print(program, type(program))
Entry = tkinter.Entry(window, width = 10, validate = 'key', validatecommand = (window.register(validate_entry), '%P', program))
Entry.place(x = 10, y = 10)
window.update()
def validate_entry(entry, program): #checks if current stuff + the amount of staff that would be added over this entry box is <= 20
print(program, type(program))
if int(entry) + program.get_stuff() <= 20:
return True
return False
program = foo() #there are other classes that create their own program and overwrite the one the entry uses, so I can't rely on this one
program.start(program)
Try this:
import tkinter as tk
class Entry(tk.Entry):
def __init__(self, master=None, args=tuple(), validatecommand=None, **kwargs):
if validatecommand is not None:
self.args = args
self.callers_function = validatecommand[0]
validatecommand = (root.register(self.validatecommand), *validatecommand[1:])
super().__init__(master, validatecommand=validatecommand, **kwargs)
def validatecommand(self, *args):
return self.callers_function(*args, *self.args)
class Foo:
def __init__(self):
pass
def validate_entry(entry, program):
print(type(entry), type(program))
return True
program = Foo()
root = tk.Tk()
# Make sure it's not `root.register(validate_entry)`:
entry = Entry(root, validate="key", validatecommand=(validate_entry, "%P"),
args=(program, ))
entry.pack()
root.mainloop()
I just made a wrapper class that will call the validatecommand with the args that were specified when creating the Entry.

PyQt - displaying widget on top of widget

I'm making an application that shows a map of an area and I'm trying to draw nodes on top of it which can represent information.
I made it all work, but did so simply by making one custom widget which I showed and printing everything again and again everytime information changed. Also I couldn't 'connect' the nodes to listeners, because they were just images in the original widget.
This made me want to reform my GUI and now I'm trying to make every class a custom widget! But there's a problem, my MapNodes aren't showing up anymore.
I searched stackoverflow and found this helpful thread:
How to set absolute position of the widgets in qt
So I have to give my mapnodes a parent, and parent = the widget that is being shown (?)
Anyway, here is my throw at it, pasting the relevant code here. Hint at where stuff might go horrible wrong: all the inits
app = QtGui.QApplication(list())
mutexbranch = Lock()
mutexnode = Lock()
def exec():
return app.exec_()
#Singleton Pattern: wanneer en object aan iets moet kunnen
# waar het inherent door de structuur niet aankon
# wordt dit via dit singleton opgelost
class GuiInternalCommunication:
realmap = 0
class MapView(QtGui.QWidget, listener.Listener):
def __init__(self, mapimagepath):
QtGui.QMainWindow.__init__(self)
listener.Listener.__init__(self)
self.map = Map(self, mapimagepath)
#self.setCentralWidget(self.map)
self.initUI()
def initUI(self):
self.setWindowTitle('Population mapping')
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.map)
self.setLayout(hbox)
resolution = QtGui.QDesktopWidget().screenGeometry()
self.setGeometry(20,20,550,800)
self.show()
######################################################################
class Map(QtGui.QWidget):
def __init__(self, parent, mapimagepath):
QtGui.QWidget.__init__(self, parent)
#self.timer = QtCore.QBasicTimer()
#coordinaten hoeken NE en SW voor kaart in map graphics van SKO
self.realmap = RealMap(
mapimagepath,
(51.0442, 3.7268),
(51.0405, 3.7242),
550,
800)
GuiInternalCommunication.realmap = self.realmap
self.needsupdate = True
self.timelabel = 0
parent.setGeometry(0,0,self.realmap.width, self.realmap.height)
self.mapNodes = {}
self.mapBranches = {}
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
rect = self.contentsRect()
#teken achtergrond
self.realmap.drawRealMap(painter)
#teken branches
mutexbranch.acquire()
try:
for branch, mapBranch in self.mapBranches.items():
mapBranch.drawMapBranch(painter)
finally:
mutexbranch.release()
######################################################################
class RealMap(QtGui.QWidget):
def __init__(self, path, coordRightTop, coordLeftBot, width, height, pixpermet = 2.6):
super(RealMap, self).__init__()
self.path = path
self.mapimage = QtGui.QImage(self.path)
self.coordLeftBot = coordLeftBot
self.coordRightTop = coordRightTop
self.width = width
self.height = height
self.realdim = self.calcRealDim()
self.pixpermet = pixpermet
def paintEvent(self, e):
painter = QtGui.QPainter()
painter.begin(self)
self.drawRealMap(self, painter)
painter.end()
def drawRealMap(self, painter):
painter.drawImage(0,0,self.mapimage)
######################################################################
class MapNode(QtGui.QWidget):
dangertocolor = {"normal":"graphics//gradients//green.png",
"elevated":"graphics//gradients//orange.png",
"danger":"graphics//gradients//red.png"}
gradimage = {"normal":QtGui.QImage(dangertocolor["normal"]),
"elevated":QtGui.QImage(dangertocolor["elevated"]),
"danger":QtGui.QImage(dangertocolor["danger"])}
btimage = QtGui.QImage("graphics//BT-icon.png")
def __init__(self, scanner, x, y, danger = 0, parent = None):
# MapNode erft over van QWidget
super(MapNode, self).__init__()
QtGui.QWidget.__init__(self, parent)
self.scanner = scanner
self.x = x
self.y = y
self.danger = 'normal'
self.calcDanger(danger)
self.grads = {}
self.grad = QtGui.QImage(MapNode.dangertocolor[self.danger])
def paintEvent(self, e):
painter = QtGui.QPainter()
painter.begin(self)
self.drawMapNode(painter)
painter.end()
def drawMapNode(self, painter):
realmap = GuiInternalCommunication.realmap
radiusm = self.scanner.range
radiusp = radiusm*realmap.pixpermet
factor = radiusp/200 # basis grootte gradiënten is 200 pixels.
grad = MapNode.gradimage[self.danger]
grad = grad.scaled(grad.size().width()*factor, grad.size().height()*factor)
painter.drawImage(self.x-100*factor,self.y-100*factor, grad)
painter.drawImage(self.x-10, self.y-10,MapNode.btimage)
painter.drawText(self.x-15, self.y+20, str(self.scanner.sensorid) + '-' + str(self.scanner.name))
######################################################################
class MapBranch:
branchpens = {"normal": QtGui.QPen(QtCore.Qt.green, 3, QtCore.Qt.DashLine),
"elevated": QtGui.QPen(QtGui.QColor(255, 51, 0), 3, QtCore.Qt.DashLine), #mandarine orange hex is 255-165-0
"danger": QtGui.QPen(QtCore.Qt.red, 3, QtCore.Qt.DashLine)}
def __init__(self, branch, mapnode1, mapnode2, danger = 0):
self.mapnode1 = mapnode1
self.mapnode2 = mapnode2
self.branch = branch
self.danger = danger
self.calcDanger(danger)
def drawMapBranch(self, painter):
painter.setPen(MapBranch.branchpens[self.danger])
painter.drawLine(self.mapnode1.x,
self.mapnode1.y,
self.mapnode2.x,
self.mapnode2.y)
EDIT - I forgot to add the code that adds the nodes. So after an event comes in the node needs to be created, this method fires creating the node:
def addNode(self, scanner):
mutexnode.acquire()
try:
coord = self.realmap.convertLatLon2Pix((scanner.latitude, scanner.longitude))
self.mapNodes[scanner.sensorid] = MapNode(scanner, coord[0], coord[1], parent = self)
self.mapNodes[scanner.sensorid].move(coord[0],coord[1])
#self.mapNodes[scanner.sensorid].show()
finally:
mutexnode.release()
I would recommend you to use the QGraphicsScene and QGraphicsItem classes for your map instead of normal QWidget classes, since they are made exactly for the purpose of displaying a large number of graphical items:
QGraphicsScene http://doc.qt.io/qt-5/qgraphicsscene.html
QGraphicsItem: http://doc.qt.io/qt-5/qgraphicsitem-members.html
From the documentation:
The QGraphicsScene class provides a surface for managing a large number of 2D graphical items.
The class serves as a container for QGraphicsItems. It is used together with QGraphicsView for visualizing graphical items, such as lines, rectangles, text, or even custom items, on a 2D surface. QGraphicsScene is part of the Graphics View Framework.
QGraphicsScene also provides functionality that lets you efficiently determine both the location of items, and for determining what items are visible within an arbitrary area on the scene. With the QGraphicsView widget, you can either visualize the whole scene, or zoom in and view only parts of the scene.
You can also embed widgets derived from QWidget in the scene, which should allow you to display practically any kind of information. As a bonus, you will get layering, fast transformations and ready-to-use handling of mouse interactions, which should be quite useful for realizing an interactive map.

wxPython basic Cairo drawing by mouse drag

I have never code in Python and trying to switch from Javascrpt/SVG. Being confused by variable scope in Python and process flow, I will appreciate any correction to those basic code to make it draw rectangle by mousedown and mouseup events. Please don't put links to instructions unless you didn't point me on errors in code.
if name=="main":
import wx
import math
class myframe(wx.Frame):
pt1 = 0
pt2 = 0
def __init__(self):
wx.Frame.__init__(self, None, -1, "test", size=(500,400))
self.Bind(wx.EVT_LEFT_DOWN, self.onDown)
self.Bind(wx.EVT_LEFT_UP, self.onUp)
self.Bind(wx.EVT_PAINT, self.drawRect)
def onDown(self, event):
global pt1
pt1 = event.GetPosition() # firstPosition tuple
def onUp(self, event):
global pt2
pt2 = event.GetPosition() # secondPosition tuple
def drawRect(self, event):
dc = wx.PaintDC(self)
gc = wx.GraphicsContext.Create(dc)
nc = gc.GetNativeContext()
ctx = Context_FromSWIGObject(nc)
ctx.rectangle (pt1.x, pt1.y, pt2.x, pt2.y) # Rectangle(x0, y0, x1, y1)
ctx.set_source_rgba(0.7,1,1,0.5)
ctx.fill_preserve()
ctx.set_source_rgb(0.1,0.5,0)
ctx.stroke()
app = wx.App()
f = myframe()
f.Show()
app.MainLoop()
Yeh, you have a problem with scopes (plus - your code isn't showing properly).
Let me give you a short example how to use members and globals in python:
# Globals are defined globally, not in class
glob1 = 0
class C1:
# Class attribute
class_attrib = None # This is rarely used and tricky
def __init__(self):
# Instance attribute
self.pt1 = 0 # That's the standard way to define attribute
def other_method(self):
# Use of a global in function
global glob1
glob1 = 1
# Use of a member
self.pt1 = 1
# Use of a class attribute
C1.class_attrib = 1
In your code you are mixing all types of variables. I think you should just make pt1 and pt2 instance attributes, so your code would look like:
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "test", size=(500,400))
self.pt1 = self.pt2 = 0
...
def onDown(self, event):
self.pt1 = event.GetPosition() # firstPosition tuple
...
You could consider reading some general tutorial like this one, to learn how Python scoping works.

How to open mutiple frames via thread in tkinter?

I tried to open mutiple frames by mutiple threads.
Here is my code.
'''
This is the module for test and studying.
Author:Roger
Date: 2010/10/10
Python version: 2.6.5
'''
import threading, Tkinter
class Application(Tkinter.Frame):
def __init__(self, master=None):
Tkinter.Frame.__init__(self, master)
self.columnconfigure(50)
self.rowconfigure(50)
self.grid()
self.createWidgets()
self.mainloop()
def createWidgets(self):
self.quitButton = Tkinter.Button (self, text='Quit', command=self.quit )
self.quitButton.grid()
class lab_404(threading.Thread):
'''
Is this the doc_string of lab_404?
Can there be mutiple_window?
I do know why is it like this?
Why is the button still on the frame?
'''
myWindow = None
def __init__(self, computer = 10, server = None, table = 1, chair = 1, student = 2, myWindow = None):
threading.Thread.__init__(self)
self.__computer = computer
self.__server = server
self.__table = table
self.__chair = chair
self.__student = student
self.myWindow = Application()
#self.myWindow.mainloop() #mainloop method is here, I don't where to put it.
def getComputer(self):
return self.__computer
def getServer(self):
return self.__server
def getMyWindow(self):
return self.myWindow
def setServer(self, Server):
self.__server = Server
def run(self):
print super(lab_404, self).getName(), 'This thread is starting now!'
print super(lab_404, self).getName(), 'This thread is ending.'
if __name__ == '__main__':
for n in xrange(1, 10, 1):
tn = lab_404(server = n) #Try to make a loop.
tn.start()
The code above has been running as a frame, then stop (mainloop?). It won't continue to the next frame until I close the formmer window. It's fitful.
How could I make it open new frames automatically?
I don't think you can do what you want. Generally speaking, there's no need to run multiple frames and multiple event loops. You can have multiple frames within a single thread and within a single event loop.

Categories