7

这个问题,简而言之:

在 MVC 中,您如何区分复选框单击(或选择框或列表框更改)与人类含义“控制器,修改模型”,以及复选框单击(或选择框或列表框更改)与控制器含义“我”我更新视图,因为模型已经改变”?


这个例子:

我有一个 JS 应用程序(都是一个大的 HTML+JS 页面;它后面有一个服务器,并且 AJAX 正在运行,但这对示例并不重要)它具有由“边缘”连接的“顶点”的概念。UI 允许您在地图上添加和删除顶点,并启用或禁用顶点对之间的边。

有两种方法可以禁用从顶点 A 到顶点 B 的边:

  1. 单击边缘,使“边缘详细信息”窗口为您提供“禁用此边缘”按钮;或者
  2. 单击顶点 A(或 B)以使“顶点详细信息”窗口为您提供附近顶点的清单,您可以从中取消选中顶点 B(或 A)。

以下是 MVC 的底层工作原理(但请参阅这篇文章的末尾以获取更新,我在其中纠正了我理解的问题):

  • 模型:顶点对象列表和边对象列表。
  • 视图:一个 GMaps UI,带有标记和折线,加上复选框和按钮以及“顶点详细信息”和“边缘详细信息”DIV。
  • 控制器:
    • 当复选框和按钮上的事件触发时更新模型的 JS 函数;和
    • 当模型上的事件触发时更新视图的 JS 函数。

这是具体的不雅

  1. 用户将 Vertex Details Window 聚焦在 Vertex A 上,Edge Details Window 聚焦在从 Vertex A 到 Vertex B 的边上。
  2. 用户在边缘详细信息窗口中单击“禁用此边缘”。
  3. 控制器函数 1 获取点击事件,并在 Edge 模型对象上调用 disable()。
  4. Edge 模型对象触发“我刚刚被禁用”事件。
  5. 控制器功能 2 收到“我刚刚被禁用”事件,并且
    1. 重新绘制边缘详细信息窗口以显示“我已禁用!” 和
    2. 在顶点详细信息窗口中取消选中顶点 B。
      1. 废话!这会再次触发控制器功能 1,它正在侦听意味着边缘被禁用的 UI 事件!

所以有一个不必要的模型的重新更新,以及视图的重新更新。在更复杂的视图中,事件触发事件触发事件,这可能会导致数十个无关更新!


更新:一个很好的答案。

我有点误解了 MVC。如上所述,我不只有一个视图:我有几个视图到几个模型中。特别是,我有一个特定节点的边缘复选框列表视图,以及一个单独的“详细窗口样式”边缘视图。

此外,我不应该让一个控制器函数在模型更改时更新所有视图:每个视图都应该在模型更改时自行修改。

因此,如果每个视图都在模型上注册“状态更新”事件,并且每个视图在收到这些事件后自行更新,那么我的循环事件问题的答案就是:

复选框列表视图将在模型状态更改后更新复选框时禁用复选框事件。

现在,如果用户通过 Edge Detail 窗口禁用 Edge,Controller 会更新 Edge Model,checkbox-list View 会收到更新通知,并且 checkbox-list View 足够聪明,可以在更改状态的同时使复选框事件静音适当的复选框。

这比我原来的解决方案更可口,其中一个控制器更新所有视图——因此必须知道哪些视图需要特别注意和馈送以避免循环。相反,只有带有麻烦的 UI 元素的单个 View 必须处理该问题。

感谢那些回答我问题的人!

4

2 回答 2

3

只是回顾一下 MVC 模型。视图通常应自行更新。它是这样工作的:控制器更改模型的状态,模型向其视图发送更新,视图从模型中拉入新状态并更新自己。虽然控制器和视图通常是捆绑在一起的(即在图形表示中向下钻取数据),但它们永远不应该直接交互,只能通过模型​​进行交互。这当然是一般的。

所以更新视图的 JS 函数实际上并不是控制器,这是一个重要的区别。它们应该被视为您观点的一部分。这可能对手头的问题没有帮助,但我认为值得指出。

您也不能删除您的模型,我假设您的意思是您正在模型中删除某些内容,因为如果没有模型支持,则实际上没有视图或控制器可以存在(或处于功能状态)。

不是 JS 代码骑师,也没有使用过 gmap,我真的不明白问题出在哪里。更改复选框(选中属性)的状态是否会触发 onClick() 事件?它真的不应该恕我直言,但也许他们以这种方式实现了它,否则您可以将控制器附加到 onClick() 并向复选框添加一些逻辑(或者,这是 JS,在某处的函数中)以更改复选框状态. 如果这不可能,选项 1 和 2 绝对是您最好的选择。

另外:用户与视图交互

那么当用户想要与视图交互时会发生什么?通常,一个小部件将同时包含视图和控制器。复选框有一个视图(您可以查看它是否被选中)和一个控制器(您可以单击它)。当您单击复选框时,原则上应发生以下情况:

  • 复选框控制器接收事件
  • 复选框控制器更改此复选框在模型中表示的值的状态
  • 模型更新监听器(包括复选框)
  • 复选框更新其外观以反映该值已更改

第一步,控制器如何接收事件在某种程度上取决于语言,但在 OOP 语言中,它可能是附加到此特定小部件上的用户界面事件的侦听器对象,该小部件要么是控制器,要么通知控制器用户交互。

于 2008-12-18T09:25:04.600 回答
0

这是困难的一个。如果我理解正确,问题的产生是因为您在模型上公开了点击处理程序,并且模型的点击事件被控制器捕获。控制器更新视图,然后切换相同的事件。

从我的角度来看,我认为控制器将自己附加到 Edge 的 Click 事件是不合适的,因为它暴露了太多关于 Edge 如何实现和使用的细节。控制器不关心 Edge 的使用方式或任何其他实现细节。

事实上,规范的 MVC 风格根本不需要控制器挂钩任何模型事件,通常是因为模型的状态不会被视图或任何其他控制器改变。模型不需要通知控制器它已被更改。

要解决您的问题,您应该将 View 的接口定义为具有单一方法,例如 ToggleEdge:


public interface GraphView
{
    event Action ToggleEdge;
}

很想创建两个方法,EdgeClicked 和 CheckboxClicked,但坚持这样的两个独立方法违反了封装原则。它向您的 Controller 或任何其他想要挂钩这些事件的人公开了太多的实现细节。请记住,Controller 只关心 View 的状态是否发生了变化,而不关心它是如何变化的。

在用户界面上实现 View 界面时,应注意确保从一个位置调用 ToggleEdge 事件。您可以通过在视图中挂钩 Edge.Clicked 事件并使用它来切换您的复选框来做到这一点;这使您的复选框负责将 Toggle vent 提升到控制器:


public class UI : UserControl, GraphView
{
    public event Action ToggleEdge;

    void OnToggleEdge(Edge edge)
    {
        if (ToggleEdge != null)
            ToggleEdge(edge);
    }

    protected void Edge_Clicked(object sender, EventArgs e)
    {
        CheckBox chkbox = FindCheckBoxThatCorrespondsToEdge((Edge)sender);
        chkbox.Checked = !chkbox.Checked;    
    }

    protected void chkEdge_CheckChanged(object sender, EventArgs e)
    {
        Edge edge = FindEdgeThatCorrespondsToCheckbox((CheckBox)sender);
        OnToggleEdge(edge);
    }
}

您可以说 View 现在对其实现了解太多了:它知道边缘和复选框从根本上是相连的。也许这是另一个 hack,但它可能会被忽略,因为“UI 逻辑”需要保持 View 的显示同步。

于 2008-12-17T18:53:55.047 回答