简体   繁体   中英

How to keep the controller alive

I'm trying to implement MVC pattern using this link . Everything's works, until I created 2nd frame with its controller. The 1st frame's controller is still alive because the code was blocked by:

app.MainLoop()

But when the 2nd controller (created in a button event), it will be GCed because its already out of context. I don't want to reference the controller to the view because I want to try Passive View approach which is the view is dumb and controller update the view, and also it would caused circular reference.

Here is the way I called the controller:

def OnClick(self, evt):
    controller = MyController2()

If I reference the controller to the parent controller like this:

def OnClick(self, evt):
    self.controller = MyController2()

The controller is still alive, but won't the controller will still be alive even after I closed the 2nd Frame? How do I keep the 2nd controller alive, but still can be GCed after its view closed? Also I would like to keep the view clean from any logic, because it already packed with widgets definitions (I have a lot of widgets in 1 Frame).

Any help would be appreciated and sorry for my bad English.

EDIT:

This is my example of a controller get garbage collected. This example use blinker instead of pubsub.

import wx

from blinker import signal

class ChildView(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)

        sizer = wx.BoxSizer()

        self.btn_child = wx.Button(self, label="click me")
        sizer.Add(self.btn_child)

        self.SetSizer(sizer)
        self.Center()

        # events
        self.btn_child.Bind(wx.EVT_BUTTON, self.on_btn_child_click)

    def on_btn_child_click(self, event):
        signal("child.btn_child_click").send(self)

class ChildController(object):
    def __init__(self, parent):
        self.view = ChildView(parent)

        self.subscribe_signal()

        self.view.Show()

    def subscribe_signal(self):
        signal("child.btn_child_click").connect(self.on_btn_child_click)

    def on_btn_child_click(self, sender):
        print "button on child clicked"

class ParentView(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)

        sizer = wx.BoxSizer()

        self.btn = wx.Button(self, label="show child window")
        sizer.Add(self.btn)

        self.SetSizer(sizer)
        self.Center()

        # events
        self.btn.Bind(wx.EVT_BUTTON, self.on_btn_click)

    def on_btn_click(self, event):
        signal("parent.btn_click").send(self)

class ParentController(object):
    def __init__(self):
        self.view = ParentView(None)

        self.subscribe_signal()

        self.view.Show()

    def subscribe_signal(self):
        signal("parent.btn_click").connect(self.on_btn_click)

    def on_btn_click(self, sender):
        child_controller = ChildController(self.view)

def main():
    app = wx.App()

    controller = ParentController()

    app.MainLoop()

if __name__ == '__main__':
    main()

As you can see, the child's button didn't work as it should, because its controller is already garbage collected (no one reference the controller). I have tried some solution like:

  1. reference the child controller in parent controller. When the child closed, the child controller will still alive unless I delete it manually or the controller replaced with new controller (re-open child window). This is especially bad if the controller hold large amount of data.

  2. circular reference between controller and view (with weakref on controller->view). This is my best bet, but I would like to avoid circular reference.

So, where should I reference the child's controller to keep it alive?

Well, this is a big topic: how to do MVC in wxPython. There is no one right answer. And with any answer you choose, you will encounter difficult choices as you attempt to follow the design pattern religiously.

Some of the examples out there suffer from not being complex enough to deal with some of these issues. Here is an example I created to try and illustrate the approach that works for me.

You can see that there is no coupling between creation of View elements and the controller, and the controller owns the logic of "setting up the application". The view is truly passive - it's elements come into existence only when needed to satisfy controller actions.

I have added two view implementations that you can choose between using a command line argument. What I am trying to show is that this is a true test of whether you have achieved good controller/view separation - you should be able to plug in a different view implementation and not have to change the controller at all. The controller depends on business logic (including the kinds of events that can happen in the application) and model APIs. It does not depend on anything in the view.

File : mvc_demo_banking_simulator.py

#!/usr/bin/env python

# This is a demo/example of how you might do MVC (Passive View) in wxpython.
import sys

import wx

from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub

from enum import Enum

import logging

import bank_view
import bank_view_separate_frames


#####
#   The control events that the Controller can process
#   Usually this would be in a module imported by each of M, V and C
#   These are the possible values for the "event" parameter of an APP_EVENT message

class AppEvents(Enum):
    APP_EXIT = 0
    APP_ADD_WORKPLACE = 1
    APP_ADD_CUSTOMER = 2
    CUSTOMER_DEPOSIT = 3
    CUSTOMER_WITHDRAWAL = 4
    CUSTOMER_EARN = 5
    PERSON_WORK = 6
    EARN_REVENUE = 7

#############
#

class Controller:
    def __init__(self, view_separate_frames):
        self._log = logging.getLogger("MVC Logger")
        self._log.info("MVC Main Controller: starting...")

        pub.subscribe(self.OnAppEvent, "APP_EVENT")

        if view_separate_frames:
            self._view = bank_view_separate_frames.MainWindow("Demo MVC - Bank Simulator")
        else:
            self._view = bank_view.MainWindow("Demo MVC - Bank Simulator")

        # Note that this controller can conceptually handle many customers and workplaces,
        # even though the current view implementations can't...

        self._customers = []
        self._workplaces = []

        # Here is the place in the controller where we restore the app state from storage,
        # or (as in this case) create from scratch

        self._customers.append(CustomerModel("Fred Nerks"))

        self._bank = BankModel()
        self._bank.CreateAccountFor(self._customers[0])

        self._view.AddBank(self._bank)
        self._view.AddCustomer(self._customers[0])

    def OnAppEvent(self, event, value=None):
        if event == AppEvents.APP_EXIT:
            self._log.info("MVC Controller: exit requested.")
            # do any necessary state saving...
            # ... then:
            sys.exit()
        elif event == AppEvents.APP_ADD_WORKPLACE:
            self._log.info("Main Controller: Add workplace requested...")
            self._workplaces.append(WorkplaceModel("Workplace %d" % (len(self._workplaces)+1) ))
            # Note: here the controller is implementing application business logic driving interactions between models
            new_workplace = self._workplaces[-1]
            for customer in self._customers:
                if customer.AcceptEmployment(new_workplace):
                    new_workplace.AddEmployee(customer)
            self._view.AddWorkplace(new_workplace)

        elif event == AppEvents.CUSTOMER_DEPOSIT:
            (the_customer, the_amount) = value
            self._log.info("customer deposit funds requested(%s)..." % the_customer.name)
            the_customer.DepositFunds(the_amount)
        elif event == AppEvents.CUSTOMER_WITHDRAWAL:
            (the_customer, the_amount) = value
            self._log.info("customer withdraw funds requested(%s)..." % the_customer.name)
            the_customer.WithdrawFunds(the_amount)
        elif event == AppEvents.CUSTOMER_EARN:
            the_customer = value
            self._log.info("customer earn requested(%s)..." % the_customer.name)
            the_customer.Work()

        elif event == AppEvents.PERSON_WORK:
            the_person = value
            self._log.info("request for customer %s to work ..." % customer.name)
            the_person.Work()
        elif event == AppEvents.EARN_REVENUE:
            self._log.info("request for sales revenue payment ...")
            the_workplace = value
            the_workplace.EarnFromSales()
        else:
            raise Exception("Unknown APP_EVENT: %s" % event)

#################
#
#  Models
#

class AccountModel:
    def __init__(self, owner, bank):
        self._balance = 0
        self._owner = owner
        self._bank = bank

    def AddMoney(self, amount):
        self._balance += amount
        self._bank.ReceiveMoney(amount)

    def RemoveMoney(self, amount):
        withdrawal_amount = min(amount, self._balance)  # they can't take out more than their account balance
        pay_amount = self._bank.PayMoney(withdrawal_amount)
        self._balance -= pay_amount
        return pay_amount


class CustomerModel:
    def __init__(self, name):
        self._log = logging.getLogger("MVC Logger")
        self._log.info("Customer %s logging started" % name)
        self.name = name

        self._cash = 0
        self._account = None
        self._employer = None

    def GetAccount(self, account):
        self._account = account

    def CashInHand(self):
        return self._cash

    def AcceptEmployment(self, workplace):
        self._employer = workplace
        self._log.info("%s accepted employment at %s" % (self.name, workplace.name))
        return True

    def Work(self):
        if self._employer:
            self._cash += self._employer.RequestPay(self)
            pub.sendMessage("CUSTOMER_BALANCE_EVENT", value = self)
        else:
            self._log.info("%s cant work, not employed" % self.name)

    def DepositFunds(self, amount):
        deposit_amount = min(amount, self._cash) # can't deposit more than we have
        self._cash -= deposit_amount
        self._account.AddMoney(deposit_amount)
        pub.sendMessage("CUSTOMER_BALANCE_EVENT", value = self)

    def WithdrawFunds(self, amount):
        amount_received = self._account.RemoveMoney(amount)
        self._cash += amount_received
        pub.sendMessage("CUSTOMER_BALANCE_EVENT", value = self)


class BankModel:
    def __init__(self):
        self._funds = 0
        self._accounts = {}

    def Funds(self):
        return self._funds

    def CreateAccountFor(self, owner):
        new_account = AccountModel(owner, self)
        self._accounts[owner.name] = new_account
        owner.GetAccount(new_account)

    def PayMoney(self, amount):
        paid = min(self._funds, amount)
        self._funds -= paid
        pub.sendMessage("BANK_BALANCE_EVENT")
        return paid

    def ReceiveMoney(self, amount):
        self._funds += amount
        pub.sendMessage("BANK_BALANCE_EVENT")


class WorkplaceModel:
    def __init__(self, name):
        self.name = name

        self._employees = []
        self._standardWage = 10

        self._funds = 0
        self._salesRevenue = 20

    def AddEmployee(self, employee):
        self._employees.append(employee)

    def EarnFromSales(self):
        self._funds += self._salesRevenue
        pub.sendMessage("WORKPLACE_BALANCE_EVENT")

    def Funds(self):
        return self._funds

    def RequestPay(self, employee):
        # (should check if employee is legit)
        paid = min(self._funds, self._standardWage)
        self._funds -= paid
        pub.sendMessage("WORKPLACE_BALANCE_EVENT")
        return paid

##############
#
#

logging.basicConfig(level=logging.INFO)

view_separate_frames = False

if len(sys.argv) > 1:
    if sys.argv[1] == "view-separate-frames":
        view_separate_frames = True

app = wx.App()
controller = Controller(view_separate_frames)
app.MainLoop()

File bank_view.py

import wx
from enum import Enum

from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub

import logging

#####
#   The control events that the Controller can process
#   Usually this would be in a module imported by each of M, V and C
#   These are the possible values for the "event" parameter of an APP_EVENT message

class AppEvents(Enum):
    APP_EXIT = 0
    APP_ADD_WORKPLACE = 1
    APP_ADD_CUSTOMER = 2
    CUSTOMER_DEPOSIT = 3
    CUSTOMER_WITHDRAWAL = 4
    CUSTOMER_EARN = 5
    PERSON_WORK = 6
    EARN_REVENUE = 7


#################
#
#   View
#

class MainWindow(wx.Frame):

    def __init__(self,  title):
        wx.Frame.__init__(self, None, -1, title)

        self._log = logging.getLogger("MVC Logger")
        self._log.info("MVC View - separate workspace: starting...")

        self._bankStatusDisplay = None
        self._customerUIs = {}
        self._workplaceUIs = {}

        # this is where we will put display elements - it's up to the controller to add them
        self._sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.SetSizer(self._sizer)

        # but we do need one button immediately...
        add_workplace_button = wx.Button(self, label="Add Workplace")
        self._sizer.Add(add_workplace_button)
        self._sizer.Layout()

        self.Bind(wx.EVT_BUTTON, self._OnAddWorkplaceClick, add_workplace_button)

        # These are the events that cause us to update our display
        pub.subscribe(self._OnCustomerBalanceChange, "CUSTOMER_BALANCE_EVENT")
        pub.subscribe(self._OnBankBalanceChange, "BANK_BALANCE_EVENT")

        self.Show()

    def _OnAddWorkplaceClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.APP_ADD_WORKPLACE)

    def AddWorkplace(self, workplace):
        self._workplaceUIs[workplace.name] = the_ui = WorkplaceInterface(self, workplace)
        self._sizer.Add(the_ui)
        self._sizer.Layout()

    def AddBank(self, bank):
        if not(self._bankStatusDisplay):
            self._bankStatusDisplay = BankStatusDisplay(self, bank)        
            self._sizer.Add(self._bankStatusDisplay)
            self._sizer.Layout()
        else:
            raise Exception("We can only handle one bank at the moment")

    def AddCustomer(self, customer):
        self._customerUIs[customer.name] = the_ui = CustomerInterface(self, customer)
        self._sizer.Add(the_ui)
        self._sizer.Layout()

    def _OnCustomerBalanceChange(self, value):
        customer = value
        self._customerUIs[customer.name].UpdateBalance()

    def _OnBankBalanceChange(self):
        self._bankStatusDisplay.Update()


class BankStatusDisplay(wx.Panel):
    def __init__(self, parent, bank):
        wx.Panel.__init__(self, parent, style = wx.RAISED_BORDER)

        self._bank = bank

        sizer = wx.BoxSizer(wx.VERTICAL)
        label = wx.StaticText(self, label="Bank Funds")

        balance_display = wx.TextCtrl(self)
        balance_display.SetEditable(False)
        balance_display.SetValue('$' + str(bank.Funds()))

        sizer.Add(label, 0, wx.EXPAND | wx.ALL)
        sizer.Add(balance_display, 0, wx.EXPAND | wx.ALL)

        self.SetSizer(sizer)        

        self._balanceDisplay = balance_display

    def Update(self):
        self._balanceDisplay.SetValue('$' + str(self._bank.Funds()))


class CustomerInterface(wx.Panel):
    def __init__(self, parent, customer):
        wx.Panel.__init__(self, parent, style = wx.RAISED_BORDER)

        self._customer = customer
        self._standardTransaction = 5 # how much customers try to deposit and withdraw

        sizer = wx.BoxSizer(wx.VERTICAL)
        label = wx.StaticText(self, label=customer.name)

        self._balanceDisplay = wx.TextCtrl(self)
        self._balanceDisplay.SetEditable(False)
        self._balanceDisplay.SetValue('$' + str(customer.CashInHand()))

        deposit_button = wx.Button(self, label="Deposit $" + str(self._standardTransaction))
        withdraw_button = wx.Button(self, label="Withdraw $" + str(self._standardTransaction))
        earn_button = wx.Button(self, label="Earn Money")

        sizer.Add(label, 0, wx.EXPAND | wx.ALL)
        sizer.Add(self._balanceDisplay, 0, wx.EXPAND | wx.ALL)

        sizer.Add(deposit_button, 0, wx.EXPAND | wx.ALL)
        sizer.Add(withdraw_button, 0, wx.EXPAND | wx.ALL)        
        sizer.Add(earn_button, 0, wx.EXPAND | wx.ALL)

        self.Bind(wx.EVT_BUTTON, self._OnDepositClick, deposit_button)
        self.Bind(wx.EVT_BUTTON, self._OnWithdrawClick, withdraw_button)
        self.Bind(wx.EVT_BUTTON, self._OnEarnClick, earn_button)

        self.SetSizer(sizer)
        self.Show()

    def _OnDepositClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.CUSTOMER_DEPOSIT, value = (self._customer, self._standardTransaction))

    def _OnWithdrawClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.CUSTOMER_WITHDRAWAL, value = (self._customer, self._standardTransaction))

    def _OnEarnClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.CUSTOMER_EARN, value = self._customer)

    def UpdateBalance(self):
        self._balanceDisplay.SetValue('$' + str(self._customer.CashInHand()))


class WorkplaceInterface(wx.Panel):
    def __init__(self, parent, workplace):
        wx.Panel.__init__(self, parent, style = wx.RAISED_BORDER)

        self._workplace = workplace

        sizer = wx.BoxSizer(wx.VERTICAL)
        label = wx.StaticText(self, label="Workplace Funds")

        self._balanceDisplay = wx.TextCtrl(self)
        self._balanceDisplay.SetEditable(False)
        self._balanceDisplay.SetValue('$' + str(workplace.Funds()))

        revenue_button = wx.Button(self, label="Earn Revenue")

        sizer.Add(label, 0, wx.EXPAND | wx.ALL)
        sizer.Add(self._balanceDisplay, 0, wx.EXPAND | wx.ALL)

        sizer.Add(revenue_button, 0, wx.EXPAND | wx.ALL)

        self.SetSizer(sizer)
        self.Show()

        self.Bind(wx.EVT_BUTTON, self._OnRevenueClick, revenue_button)

        pub.subscribe(self._OnBalanceChange, "WORKPLACE_BALANCE_EVENT")

    def _OnRevenueClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.EARN_REVENUE, value = (self._workplace))

    def _OnBalanceChange(self):
        self._balanceDisplay.SetValue('$' + str(self._workplace.Funds()))

File : bank_view_separate_frames.py

import wx
from enum import Enum

from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub

import logging

#####
#   The control events that the Controller can process
#   Usually this would be in a module imported by each of M, V and C
#   These are the possible values for the "event" parameter of an APP_EVENT message

class AppEvents(Enum):
    APP_EXIT = 0
    APP_ADD_WORKPLACE = 1
    APP_ADD_CUSTOMER = 2
    CUSTOMER_DEPOSIT = 3
    CUSTOMER_WITHDRAWAL = 4
    CUSTOMER_EARN = 5
    PERSON_WORK = 6
    EARN_REVENUE = 7


#################
#
#   View
#

class MainWindow(wx.Frame):

    def __init__(self,  title):
        wx.Frame.__init__(self, None, -1, title)

        self._log = logging.getLogger("MVC Logger")
        self._log.info("MVC View - separate workspace: starting...")

        self._bankStatusDisplay = None
        self._customerUIs = {}
        self._workplaceUIs = {}

        # this is where we will put display elements - it's up to the controller to add them
        self._sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.SetSizer(self._sizer)

        # but we do need one button immediately...
        add_workplace_button = wx.Button(self, label="Add Workplace")
        self._sizer.Add(add_workplace_button)
        self._sizer.Layout()

        self.Bind(wx.EVT_BUTTON, self._OnAddWorkplaceClick, add_workplace_button)

        # These are the events that cause us to update our display
        pub.subscribe(self._OnCustomerBalanceChange, "CUSTOMER_BALANCE_EVENT")
        pub.subscribe(self._OnBankBalanceChange, "BANK_BALANCE_EVENT")

        self.Show()

    def _OnAddWorkplaceClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.APP_ADD_WORKPLACE)

    def AddWorkplace(self, workplace):
        self._workplaceUIs[workplace.name] = WorkplaceInterface(self, workplace)

    def AddBank(self, bank):
        if not(self._bankStatusDisplay):
            self._bankStatusDisplay = BankStatusDisplay(self, bank)        
            self._sizer.Add(self._bankStatusDisplay)
            self._sizer.Layout()
        else:
            raise Exception("We can only handle one bank at the moment")

    def AddCustomer(self, customer):
        self._customerUIs[customer.name] = CustomerInterface(self, customer)

    def AddWorkplace(self, workplace):
        self._theWorkplaceUI = WorkplaceInterface(workplace)

    def _OnCustomerBalanceChange(self, value):
        customer = value
        self._customerUIs[customer.name].UpdateBalance()

    def _OnBankBalanceChange(self):
        self._bankStatusDisplay.Update()


class BankStatusDisplay(wx.Panel):
    def __init__(self, parent, bank):
        wx.Panel.__init__(self, parent, style = wx.RAISED_BORDER)

        self._bank = bank

        sizer = wx.BoxSizer(wx.VERTICAL)
        label = wx.StaticText(self, label="Bank Funds")

        balance_display = wx.TextCtrl(self)
        balance_display.SetEditable(False)
        balance_display.SetValue('$' + str(bank.Funds()))

        sizer.Add(label, 0, wx.EXPAND | wx.ALL)
        sizer.Add(balance_display, 0, wx.EXPAND | wx.ALL)

        self.SetSizer(sizer)        

        self._balanceDisplay = balance_display

    def Update(self):
        self._balanceDisplay.SetValue('$' + str(self._bank.Funds()))


class CustomerInterface(wx.Frame):
    def __init__(self, parent, customer):
        wx.Frame.__init__(self, None, -1, customer.name, size = (200,300))

        self._customer = customer
        self._standardTransaction = 5 # how much customers try to deposit and withdraw

        sizer = wx.BoxSizer(wx.VERTICAL)
        label = wx.StaticText(self, label=customer.name)

        self._balanceDisplay = wx.TextCtrl(self)
        self._balanceDisplay.SetEditable(False)
        self._balanceDisplay.SetValue('$' + str(customer.CashInHand()))

        deposit_button = wx.Button(self, label="Deposit $" + str(self._standardTransaction))
        withdraw_button = wx.Button(self, label="Withdraw $" + str(self._standardTransaction))
        earn_button = wx.Button(self, label="Earn Money")

        sizer.Add(label, 0, wx.EXPAND | wx.ALL)
        sizer.Add(self._balanceDisplay, 0, wx.EXPAND | wx.ALL)

        sizer.Add(deposit_button, 0, wx.EXPAND | wx.ALL)
        sizer.Add(withdraw_button, 0, wx.EXPAND | wx.ALL)        
        sizer.Add(earn_button, 0, wx.EXPAND | wx.ALL)

        self.Bind(wx.EVT_BUTTON, self._OnDepositClick, deposit_button)
        self.Bind(wx.EVT_BUTTON, self._OnWithdrawClick, withdraw_button)
        self.Bind(wx.EVT_BUTTON, self._OnEarnClick, earn_button)

        self.SetSizer(sizer)
        self.Show()

    def _OnDepositClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.CUSTOMER_DEPOSIT, value = (self._customer, self._standardTransaction))

    def _OnWithdrawClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.CUSTOMER_WITHDRAWAL, value = (self._customer, self._standardTransaction))

    def _OnEarnClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.CUSTOMER_EARN, value = self._customer)

    def UpdateBalance(self):
        self._balanceDisplay.SetValue('$' + str(self._customer.CashInHand()))


class WorkplaceInterface(wx.Frame):
    def __init__(self, workplace):
        wx.Frame.__init__(self, None, -1, workplace.name, size = (200,200))

        self._workplace = workplace

        self._panel = wx.Panel(self, style = wx.RAISED_BORDER)

        sizer = wx.BoxSizer(wx.VERTICAL)
        label = wx.StaticText(self._panel, label="Funds")

        self._balanceDisplay = wx.TextCtrl(self._panel)
        self._balanceDisplay.SetEditable(False)
        self._balanceDisplay.SetValue('$' + str(workplace.Funds()))

        revenue_button = wx.Button(self._panel, label="Earn Revenue")

        sizer.Add(label, 0, wx.EXPAND | wx.ALL)
        sizer.Add(self._balanceDisplay, 0, wx.EXPAND | wx.ALL)

        sizer.Add(revenue_button, 0, wx.EXPAND | wx.ALL)

        self._panel.SetSizer(sizer)

        self.Bind(wx.EVT_BUTTON, self._OnRevenueClick, revenue_button)

        pub.subscribe(self._OnBalanceChange, "WORKPLACE_BALANCE_EVENT")

        self.Show()

    def _OnRevenueClick(self, event):
        pub.sendMessage("APP_EVENT", event = AppEvents.EARN_REVENUE, value = (self._workplace))

    def _OnBalanceChange(self):
        self._balanceDisplay.SetValue('$' + str(self._workplace.Funds()))

Random notes:

  • I have named the model classes "ThingoModel". Normally you would just name them Thingo - the 'model' is implied, but I did it for hopefully clarity here.

  • Similarly, the names of the components of the View are chosen to emphasise their view-role.

  • This example shows the components of the View notifying the Controller about what the User has requested via pubsub messages ("App Events")

  • It shows the models notifying "whoever cares" (the view) about events that might need a view change using pubsub messages (model specific events).

  • It has the Controller telling the View what to do using method calls

  • It is very careful not to have the View access models directly. The View has handles to (references to) models for the purpose of displaying information from them. In C++ these would be const thingoes that the View can't change. In python all you can do is have the discipline to make sure that the View reads stuff from models, but notifies everything to the Controller

  • This code extends to supporting multiple customers very easily. You should be able to just create more in the controller and (fingers crossed) they'd be handled

  • The code assumes there's only one bank.

I have written a couple of large wxpython apps using this basic pattern. When I started, I constructed the first of these apps as an experiment in MVC-P. I became totally converted to this approach when unexpected circumstances forced me to completely redo the layout of the GUI. Because no business logic was in my View, this was entirely tractable, and the re-done app was available relatively quickly and with few feature regressions.

Thanks to Yoriz's example, I have an idea to create a class that inherit the view and reference the controller, so that the controller won't be garbage collected immediately.

This new class will handle all the view's events and call the controller's method directly, and also have all the method to update the view. The controller will use signal to communicate to the view, and this class will listen that signal and update the view.

When another view need to be created, the event will call the controller's method with the view itself as parameter, then the controller will create another view and controller with its view from parameter as the parent.

Thanks everyone for the contribution, if there are any better answer I'll accept it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM