【问题标题】:How to keep the controller alive如何让控制器保持活力
【发布时间】:2014-03-05 08:57:07
【问题描述】:

我正在尝试使用 this link 实现 MVC 模式。一切正常,直到我用它的控制器创建了第二帧。第一帧的控制器还活着,因为代码被阻止了:

app.MainLoop()

但是当第二个控制器(在按钮事件中创建)时,它将被 GC,因为它已经脱离了上下文。我不想将控制器引用到视图,因为我想尝试被动视图方法,即视图是哑的,控制器更新视图,也会导致循环引用。

这是我调用控制器的方式:

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

如果我像这样将控制器引用到父控制器:

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

控制器还活着,但是即使我关闭了第 2 帧,控制器是否仍然活着?如何使第二个控制器保持活动状态,但在其视图关闭后仍然可以进行 GC?此外,我想保持视图不受任何逻辑影响,因为它已经包含了小部件定义(我在 1 帧中有很多小部件)。

如果我的英语不好,我们将不胜感激。

编辑:

这是我收集垃圾的控制器示例。此示例使用 blinker 而不是 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()

如您所见,子按钮无法正常工作,因为它的控制器已经被垃圾回收了(没有人引用该控制器)。我尝试了一些解决方案,例如:

  1. 在父控制器中引用子控制器。 当孩子关闭时,除非我手动将其删除或将控制器替换为新控制器(重新打开子窗口),否则子控制器将仍然存在。如果控制器保存大量数据,这尤其糟糕。

  2. 控制器和视图之间的循环引用(控制器->视图上的弱引用)。 这是我最好的选择,但我想避免循环引用。

那么,我应该在哪里引用孩子的控制器以使其保持活动状态?

【问题讨论】:

  • 你没有分享足够多的代码来理解你在说什么。但是,遗憾的是,您指向的链接是如何正确执行 MVC 的一个非常糟糕的示例。除了 pubsub.subscribe 之外,控制器中不应该有任何小部件或 wx 相关调用。 wx 的东西属于视图。如果你做对了,控制器将永远不会被创建来响应一个 gui 事件。控制器存在于视图之前。视图从属于控制器。
  • @GreenAsJade 感谢您的回复,您能给我推荐任何正确执行 MVC 的好例子吗?最好使用pubsub或blinker。我还在学习 MVC,所以我需要任何好的资源来学习。
  • 我在想这个 - 我几乎倾向于写一个!我不知道哪里是好的。
  • 我有一个 WxPython MVC 示例,你可以看看它有一个非常被动的 gui,因为我自己写了它,我不能保证它有多好,但它可能会有所帮助。 github.com/Yoriz/WxPython-Downloader 它使用 github.com/Yoriz/Y_Mvc
  • @Yoriz 谢谢,我试过了再报告。

标签: python model-view-controller wxpython


【解决方案1】:

嗯,这是一个很大的话题:如何在 wxPython 中做 MVC。没有一个正确的答案。对于您选择的任何答案,当您尝试虔诚地遵循设计模式时,您将遇到困难的选择。

那里的一些示例不够复杂,无法处理其中一些问题。这是我创建的一个示例,旨在尝试说明适合我的方法。

可以看到View元素的创建和控制器之间没有耦合,控制器拥有“设置应用”的逻辑。视图是真正被动的——它的元素只有在需要满足控制器操作时才会出现。

我添加了两个视图实现,您可以在使用命令行参数之间进行选择。我想展示的是,这是对您是否已实现良好的控制器/视图分离的真正测试——您应该能够插入不同的视图实现,而根本不必更改控制器。控制器依赖于业务逻辑(包括应用程序中可能发生的事件类型)和模型 API。它不依赖于视图中的任何内容。

文件: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()

文件 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()))

文件: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()))

随机笔记:

  • 我已将模型类命名为“ThingoModel”。通常,您只需将它们命名为 Thingo - 隐含“模型”,但我这样做是为了希望清楚。

  • 同样,视图组件的名称被选择以强调它们的视图角色。

  • 此示例显示了 View 的组件,它通过 pubsub 消息(“应用程序事件”)通知控制器用户请求的内容

  • 它显示模型通知“谁关心”(视图)有关可能需要使用 pubsub 消息(模型特定事件)更改视图的事件。

  • 控制器通过方法调用告诉视图做什么

  • 非常小心不要让 View 直接访问模型。视图具有模型的句柄(引用),以显示来自模型的信息。在 C++ 中,这些将是视图无法更改的常量。在 python 中,你所能做的就是确保视图读取模型中的内容,但将所有内容通知给控制器

  • 此代码很容易扩展到支持多个客户。您应该能够在控制器中创建更多内容,并且(手指交叉)它们将被处理

  • 代码假定只有一个银行。

我已经使用这个基本模式编写了几个大型 wxpython 应用程序。当我开始时,我构建了这些应用程序中的第一个作为 MVC-P 的实验。当意外情况迫使我完全重做 GUI 的布局时,我完全转变为这种方法。因为在我的视图中没有业务逻辑,所以这完全易于处理,并且重新完成的应用程序可以相对快速地使用并且几乎没有功能回归。

【讨论】:

  • 这是一个很棒的话题,正如我所说的——一个很大的话题。如果您阅读 MVC,您会发现许多关于它的含义以及如何实现它的观点 - 让我们面对现实:它只是达到目的的一种手段。 “结束”是“如何让我们的应用程序更简单、更健壮”。上面的代码和主题有很多方面值得讨论。我创建了chat.stackoverflow.com/rooms/49231/mvc-design-in-python 作为一个可以聊天的地方,如果有人感兴趣的话。
  • 您的示例非常接近我想要实现的目标。你能告诉我一个父窗口的例子,它显示了有自己的控制器的子窗口吗?那里的所有示例总是使用 1 个窗口。我用一个例子更新了我的问题。
  • 我很想知道将控制器数量与视图窗口数量联系起来的动机。这“破坏”了 MVC - 在 MVC 中应该能够在不更改控制器的情况下完全“重做”视图。为什么不只有一个控制器?
  • 如果您有一个需要一些子视图的视图,这将是压倒性的,例如在交易列表视图中,它需要交易详细信息视图来显示交易详细信息并更新它,在交易详细信息视图中需要项目列表视图设置项目和客户列表视图设置客户。并且事务列表视图还有一个显示所有菜单的父根视图。我说的是需要大量视野的会计项目或库存管理项目。
  • 一旦有机会,我将更新示例以展示如何完成两个控制器。同时,如果您对该主题感兴趣,我将有兴趣继续讨论这是否是一个好主意。 chat.stackoverflow.com/rooms/49231/mvc-design-in-python
【解决方案2】:

感谢 Yoriz 的例子,我有了一个想法,即创建一个继承视图并引用控制器的类,这样控制器就不会立即被垃圾回收。

这个新类将处理所有视图的事件并直接调用控制器的方法,并拥有更新视图的所有方法。控制器将使用信号与视图通信,该类将监听该信号并更新视图。

当需要创建另一个视图时,该事件会以视图本身为参数调用控制器的方法,然后控制器将创建另一个视图和控制器,并将其视图作为父视图。

感谢大家的投稿,如果有更好的答案我会采纳的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-09
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 2023-01-27
    • 1970-01-01
    • 2019-09-28
    相关资源
    最近更新 更多