【问题标题】:Delegate painting of custom Swing component to parent将自定义 Swing 组件的绘制委托给父级
【发布时间】:2015-03-14 08:16:05
【问题描述】:

我的目标

我希望我的自定义 Swing 组件 CellLabel extends JLabel 始终绘制某些东西 (X),而某些其他元素 (Y) 只有当它的父级 - (a JPanel 持有 CellLabels 的网格) - 告诉它。 (Y) 的绘制取决于网格中相邻单元格的状态,只有JPanel 可以获取此信息并决定是否以及如何为每个孩子绘制 (Y) @ 987654326@.

我的问题

我怎样才能将绘画行为具体化(到一个带有参数的方法中,该方法允许我描述如何准确地绘画(Y)),以便父母可以决定是否需要CellLabel 孩子画(Y)还是不画?

我的问题

那里的每个 Swing 教程都告诉我应该在创建自定义组件时覆盖 paintComponent 方法;但是,在我的应用程序中,组件无法自行决定是否以及如何绘制 (Y),因为它缺少必要的信息。

我尝试编写一个 CellLabel::paintY(int offset) 方法,一旦它决定了如何以及是否绘制 (Y),我可以从父级调用它:

class CellLabel extends JLabel {
    void paintComponent(Graphics g) {
        super.paintComponent(g);
        // Paint elements (X) which should always be painted, independent
        // of external state
    }        

    void paintY(int[] params){
        Graphics2D g2 = (Graphics2D) this.getGraphics();

        // Perform some calculation with params that decide how painting is done

        // (*) Actually paint elements (Y) by using the g2 graphics context
    }
}

但是,我似乎无法在约定告诉我覆盖的预定义 paint 方法之外获得 Graphics 上下文。我在 (*) 处得到以下异常(实际尝试使用 g2 绘制时):

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at CellLabel.paintY(CellLabel.java:77)
at AutomatonUI.createAndShowGUI(AutomatonUI.java:50)
at AutomatonUI.access$000(AutomatonUI.java:22)
at AutomatonUI$1.run(AutomatonUI.java:29)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:744)
at java.awt.EventQueue.access$400(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:697)
at java.awt.EventQueue$3.run(EventQueue.java:691)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:714)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

我在编写业务逻辑和数字运算方面拥有丰富的经验,但总的来说我对 Swing(和 UI 编程)还很陌生,所以如果我做的事情很糟糕,请原谅(并教育我)错了。

【问题讨论】:

  • 你的自定义组件不应该以这种方式依赖于父组件,相反,它应该有一个共享模型,它可以询问有关 UI 状态的问题。然后将为每个标签提供确定状态所需的信息(例如它在网格中的当前 x/y 位置)
  • 对于example

标签: java swing user-interface custom-component


【解决方案1】:

更好的方法可能是将您的逻辑与 Swing GUI 库分开。例如,定义一个包含Cell 对象的CellContainer 类,可以在每次渲染之前对其进行更新(在更新中,您可以根据需要从CellContainer 更新Cells)。然后,您可以拥有 CellContainerPanelCellLabel 类,它们扩展了适当的 Swing 类,它们引用了 CellContainerCell,每个都只是绘制了它们的引用可用的状态。

【讨论】:

  • 由于Graphics 是共享资源,处理来自父级的引用将为您提供单元格面板将使用的相同Graphics 实例。由于翻译是在以任何方式绘制组件之前自动完成的......这一切都只是重复正常完成的工作。话虽如此,您的第二个建议要好得多,并且符合Model-View-Controller 范式的概念
  • @MadProgrammer 我错误地认为 JPanel 是在它的孩子之后绘制的,并试图扩展他的工作示例而不是先绘制......虽然我会删除它,但这是不正确的。
  • 如果我理解正确,这意味着每个Cell 引用都必须包含依赖于其邻居的状态信息——这正是我想要避免的冗余。正如@MadProgrammer 所建议的那样,我看到这如何使一切都与 MVC 保持一致,并且理解将业务逻辑与 UI 分离是一件好事。感谢您的提示,我希望我可以毫不费力地实施您的建议。对于初学者,在使用paintComponent 绘画时让CellLabel 检查Cell 状态是否是个好主意?
  • @MHaaZ 为什么每个单元都需要知道什么?知道邻居在做什么不是单元的责任,而是由单元的“容器”决定的。首先将您的逻辑与您的视图分离,允许视图呈现逻辑/状态和逻辑/状态的要求以分别管理。您通过将每个组件彼此解耦,将逻辑移除到不同的层,它应该能够独立于 UI 运行,并且 UI 应该能够使用该模型的多个不同实例
  • @MadProgrammer 分离逻辑/状态和视图正是我正在尝试(并且失败)做的事情。 CellContainer 是知道Cell 附近的外观的人,因此它应该部分控制CellLabels 中的绘画(他们不能自己做:如果他们指向的Cell 没有'对它的邻居一无所知(我同意它不应该),CellLabel 怎么能完全绘制自己?)我回到第一点:如何告诉 Swing 组件(CellLabel)绘制一些东西取决于其Cell 参考中未包含的某些外部状态(邻居)?
【解决方案2】:

IIRC,你的paintComponent函数需要调用super.paintComponent(g);

上次我用 swing 编写程序时,我在一个类中声明了一个静态图形对象,所有东西都可以访问(这可能是一件可怕的事情,但它是一种解决方法,它帮助我完成了我的任务)。与其将信息存储在二维数组中,不如将“信息”对象存储在数组中。这些信息对象(例如对象 x)可以包含有关单元格本身的所有信息,以及“draw(g)”方法。该绘制方法可以决定如何绘制该单元格。在paintComponent函数中,你现在可以调用

//where g is the graphics object
for (array x : array of arrays) {
    for (informationObject y : x) {
        y.draw(g);
    }
}

现在您可以在对象本身内部进行所有计算,而不必处理获取外部信息并在外部进行计算。 (然后,代码也将更容易调试)

【讨论】:

  • 是的,我就是这么做的。我会在代码中反映这一点,谢谢提醒:-)
  • 您是否将 y 单元格(可以编码为具有“绘制我”功能的对象)存储在任何类型的列表中?如果您选择这样做,您可以使用增强的 for 循环来绘制这些单元格。
  • 包含“重要”信息(用于决定绘制什么以及如何绘制)的单元格存储在一个二维数组中。您能否指定将单元格存储为具有“绘制我”功能的对象的含义?该函数将如何绘制/绘制(使用哪个Graphics 上下文?)
猜你喜欢
  • 1970-01-01
  • 2013-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多