0

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()
Spencer H
  • 653
  • 3
  • 12
  • 30
  • What is controller's class? – daragua Feb 07 '17 at 19:01
  • I guess it would make sense to have a `Page` object which contains the data and is shared by both `Frame` objects. – Peter Wood Feb 07 '17 at 19:03
  • @daragua Code updated to show controller class – Spencer H Feb 07 '17 at 19:04
  • @peterwood I'd create a new instance of `page` when I set the data, but how would that get passed with the lambda? – Spencer H Feb 07 '17 at 19:06
  • `Project` could have a `Page` instance and pass it to `F(container, self, page)`, for example. – Peter Wood Feb 07 '17 at 19:11
  • 1
    Incapsulate lambda in call context like this: command=lambda: PlotPageCall(PlotPage, x_data, y_data). Then inside PlotPageCall init/save all data and pass it to the next page. – Dmitry Shilyaev Feb 07 '17 at 19:21
  • @DmitryShilyaev If I create `class PlotPageCall(PlotPage, x_data, y_data): def __init__(self,parent,controller): [save variables]` then `controller.show_frame(PlotPage)` I haven't solved the issue yet. x_data and y_data still need to get passed to PlotPage for it to know what to plot – Spencer H Feb 07 '17 at 19:30
  • 1
    @SpencerH. it was basic considerations. You've asked "the best way to pass the data to PlotPage". Try to omit globals, incapsulate. For example, you can manage kind of context inside your Project. If we treat Project as a workflow, then it must manage some shared state among steps. – Dmitry Shilyaev Feb 07 '17 at 19:44

1 Answers1

2

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.

daragua
  • 1,133
  • 6
  • 8
  • I appreciate your input on the Observer pattern! Along with this and [How to get variable data from a class](http://stackoverflow.com/questions/32212408/how-to-get-variable-data-from-a-class) I was able to resolve the issue – Spencer H Feb 08 '17 at 14:09
  • 1
    I'm glad it helped. Concerning the implementation of the Observer pattern, if you do it yourself, there is a common pitfall : you remove an observer but somehow it still lives. It has leaked because the callback stored in the Observable holds a reference to it. It is important to take care to delete callbacks installed by an observer once this one is to be removed. – daragua Feb 08 '17 at 16:20