Swing 的 JList 的 MVC 实现有问题吗?

Posted

技术标签:

【中文标题】Swing 的 JList 的 MVC 实现有问题吗?【英文标题】:Is there something wrong with Swing's MVC implementation for JList? 【发布时间】:2011-06-10 14:32:55 【问题描述】:

前段时间我问过this question。所有解决方案都是变通方法。

现在不可能了。我觉得这里有问题,但我不知道是Swing的MVC模型在概念上是错误的,还是我的想法在概念上是错误的。

问题又来了。我正在使用JList 来实现文档页面的缩略图列表。如果用户从列表中选择另一个缩略图,则加载该页面。为此,我将ListSelectionListenerJList,它在选择更改时,它加载该页面。但用户也可以使用另一个控件更改页面。自然,我希望通过在此处选择该页面来将其反映在缩略图列表中。所以我setSelectedIndex() 来更新JList。不幸的是,这产生了引发ListSelectionEvent 的不良影响,导致侦听器重新加载页面。

现在这里出了什么问题?我只是从其他地方更改了模型,所以我自然希望视图自行更新,但我不希望它触发事件。 Swing 没有实现 MVC 对吗?还是我在这里漏掉了一点?

【问题讨论】:

感谢大家的精彩回答!我接受了@britishmutt 的回答,因为它最详细、最有见地,并且包含最干净的解决方案。问题是加载页面的组件应该看到它被请求加载相同的页面并且不应该这样做。这些链接非常有用。我还是觉得 Swing 的 MVC 模型有缺陷。他们应该走传统的路。他们的模型似乎比它的价值更麻烦。 我在监听器更新中多次遇到同样的问题。想象一下,如果您有 N 个必须相互更新的组件......即使您检查真实的显示更改以决定是否触发事件,之后其他 N-1 个组件也会触发 N-1 个事件他们得到更新。 【参考方案1】:

这是我们许多 Swing 程序员必须面对的一个问题:多个控件修改相同的数据,然后更新反映在每个控件中。在某些时候,某些东西必须最终否决将应用于模型的更新:无论是什么东西都需要能够处理多个(可能是冗余甚至矛盾的)更新并决定如何处理它们。这可能发生在模型层,但理想情况下它应该是执行此操作的控制器 - 毕竟,这部分很可能是业务逻辑所在的位置。

在这方面,Swing 的问题在于 MVC 的控制器部分经常在视图组件和模型之间进行一些拆分,因此很难集中该逻辑。在某种程度上,Action 接口通过将逻辑放在一个地方并允许它由不同的组件共享来纠正 actionPerformed() 事件,但这对其他类型的事件没有帮助,或者当有多个需要协调的不同类别的事件。

因此,答案是遵循 Swing 中暗示但未明确说明的模式:仅在状态实际更改时才执行请求的更新,否则不执行任何操作。这方面的一个例子是JList 本身:如果您尝试将JList 的选定索引设置为已选定的相同索引什么都不会发生。不会触发任何事件,不会发生更新:更新请求被有效地忽略。这是一件好事。这意味着您可以,例如,在您的 JList 上设置一个监听器,它会响应新选择的项目,然后反过来要求相同的 JList 重新选择相同的项目,您就不会陷入困境病态递归循环。如果应用程序中的所有模型控制器都这样做,那么在整个地方触发多个重复事件就没有问题 - 每个组件只会在需要时更新自己(并随后触发事件),如果它确实更新然后它可以触发它想要的所有更新事件,但只有那些尚未收到消息的组件才会对其执行任何操作。

【讨论】:

感谢详细的回答;它提供了很多见解。我担心我是唯一一个遇到这个问题的人,这让我很烦恼,因为它又出现了几次。你在最后一段中建议的是最初发生的事情。但是虽然它没有进入无限循环,但它仍然加载了两次页面。我也不喜欢到处乱跑的所有这些事件。至少我很高兴这是一个普遍的问题,而且我现在感觉不那么愚蠢了。【参考方案2】:

这是预期的行为。

来自Model-View-Controller [Wikipedia]:

在事件驱动系统中,模型 通知观察者(通常是视图) 当信息发生变化时 他们可以做出反应。

因此,当您在 JList 上调用 setSelectedIndex 时,您正在更新其模型,然后通知每个 ListSelectionListener。如果您可以“悄悄地”更新模型而不让任何人知道,那就不是 MVC。

【讨论】:

伟大的报价!你是绝对正确的。但是现在我想起来了,JList 是一个视图,所以它必须更新。但是 ListSelectionListener 不是视图。它是一个控制器。所以我认为这是问题所在。就像@OscarRyz 所说,控制器和视图密切相关。实际上,控制器与模型耦合。奇怪的!因为控制器应该监听用户操作(比如点击另一个项目),而不是模型的变化。所以我认为这就是问题所在。应该有类似的 addItemClickedListener() 而不是 addListSelectionListener()。【参考方案3】:

Swing 不完全是 MVC,但它源于 MVC(不同之处在于,Swing 中的视图和控制器比其他 MVC 中的视图和控制器更密切相关,请参阅Swing architecture 了解更多详细信息)。

但这似乎不是您面临的问题。听起来您必须在事件侦听器中验证事件的类型并决定是否忽略它:如果事件起源于列表中,请更改它。如果是由其他控件触发的,则不要。

【讨论】:

我想我无法检查它的来源。该事件是由 JList(或者更准确地说是它的模型)创建的,但是如果用户单击一个项目,这也是一样的。该事件在此之前没有任何信息。 重要的不是它来自哪里,而是事件是否会导致模型状态发生变化,从而导致显示发生变化。无论哪一段代码最终负责刷新文档的显示,都需要知道当前正在显示什么文档,如果要求再次显示相同的文档,它应该忽略该事件。【参考方案4】:

@dogbane 说了什么。

但要解决此问题,您需要在侦听器期间添加某种状态检查,以查看该事件是否是您应该忽略的事件。 ListSelectionEvent 有一个 getValueAdjusting() 方法,但这主要是内部的。你需要做的是自己模拟它。

因此,例如,当您从外部选择更新列表时,您将拥有类似...的代码

try 
    setSelectionAdjusting(true);
    /* ... your old update code ... */
 finally 
    setSelectionAdjusting(false);

在 ListSelectionListenerEvent 的更新代码中

public void valueChanged(ListSelectionEvent e) 
    if (!isSelectionAdjusting()) 
        /* ... do what you did before ...*/
    

范围界定和访问问题留给读者作为练习。您必须编写 setSelectionAdjusting,并可能将其设置在其他对象上。

【讨论】:

对我来说听起来像是另一个 hack。我仍然觉得这里在概念上有些问题。【参考方案5】:

我仍然觉得这里在概念上有些问题。

我很同情,但考虑到您没有简单的JList 观察ListSelectionModel,这可能会有所帮助。相反,您有一个JList 和其他一些控制观察混合选择模型。在@Taisin's example 中,混合是CustomSelectionModel 扩展DefaultListSelectionModel 并允许静默更改。兼容时,还可以共享模型,如本教程中的 question & answer 和 SharedModelDemo 中的建议。

作为参考,此thread 引用了文章Java SE Application Design With MVC: Issues With Application Design,该文章更详细地解决了该问题。

【讨论】:

感谢您的同情!是的,我认为这似乎是最干净的解决方案。但如果你仔细想想,就好像你正在解决 Swing 模型中的那个概念错误。在您提到的文章中,这是第 3 步:The model is updated. It notifies the controller of its property change. 这不是 MVC。该模型永远不会通知控制器。它通知视图。通过覆盖模型以不引发更新,您可以有效地修复第 3 步中存在的这个错误。 你说得对,它不是纯 MVC;这是一个可分离的模型架构,被@OscarRyz 引用:java.sun.com/products/jfc/tsc/articles/architecture/#separable 我认为这里更清楚:oracle.com/technetwork/articles/javase/index-142890.html#3 我不知道他们为什么选择这种设计。他们说的原因是“使用这个修改后的 MVC 有助于更完全地将模型与视图解耦。”但为什么这样好?它解决了什么问题?目前,对我来说,这似乎更麻烦。【参考方案6】:

我总是这样:

    public class MyDialog extends JDialog 
        private boolean silentGUIChange = false;

    public void updateGUI 
        try 
            silenGUIChange = true;

            // DO GUI-Updates here:
            textField.setText("...");
            checkBox.setSelected (...);

        
        finally 
            silentGUIChange = false;
        
    

    private void addListeners () 
        checkBox.addChangeListener (new ChangeListener () 
           public void stateChanged (ChangeEvent e) 
              if (silentGUIChange)
                 return;

              // update MODEL
              model.setValue(checkBox.isSelected());
          
         );
    


【讨论】:

【参考方案7】:

通常,监听器的工作方式是每次它等待的事件发生时都会“关闭”。如果我不得不推测这是您对事情的误解。

【讨论】:

以上是关于Swing 的 JList 的 MVC 实现有问题吗?的主要内容,如果未能解决你的问题,请参考以下文章

java swing 中的列表框JList如何在程序中动态的添加和删除元素

Swing JList SetCellRenderer 背景颜色不起作用

是否可以更改 Java Swing jList 中项目的名称*显示*?

如何在Java Swing中创建响应式JList

java swing中jList滚动条位置问题

Java中Swing组件中的JTextArea,JList控件中的滚动条问题?帮忙解决!