【问题标题】:What is the conventional way to write a GUI in Java? [closed]用 Java 编写 GUI 的传统方法是什么? [关闭]
【发布时间】:2013-10-03 18:03:46
【问题描述】:

我已经完成了一个 GUI 应用程序。我有四个班级:Main, UserWindow, Task1, Task2Main 类包含一个布尔变量 buttonStartPressedMain 方法启动 UserWindow 类的一个实例,并等待用户按下开始按钮。当用户按下开始按钮(在UserWindow 中)时,ActionListenertrue 分配给static boolean buttonStartPressedMain 方法继续。

Main.java

public static void ......
static boolean buttonStartPressed = false;

...........

while (!buttonStartPress) {
Thread.sleep(50);
}

Task1 t1 = new Task1();

.....
}
}

效果很好,但是我不喜欢while 循环。我觉得这不是编写应用程序的常规方式。还有另一种方法:我可以结合MainUserWindow 类,ActionListener (buttonPressed) 的结果将是Task1 的开始。但是,另一方面,我认为Main 类和UserWindow 类应该彼此分开。

【问题讨论】:

  • 你考虑过 Swing 教程吗?
  • 你的问题是..?
  • 我不明白调用关闭的原因如何适用于这个问题。 OP 确实 对问题的了解很少,因为他找到了解决问题的方法。他只是(理所当然地)怀疑他的解决方案是一个好的解决方案。我也不觉得这个问题缺少信息。他的意图和他当前的解决方案都得到了明确的解释。
  • @barjak 我也认为这个问题不应该被关闭,尽管它可能更适合codereview.stackexchange.comprogrammers.stackexchange.com。但是很多问题都与这三个重叠,我认为应该有一个合并的观点。这个可以搬吗?

标签: java swing user-interface actionlistener thread-sleep


【解决方案1】:

是的,那是错误的。你不应该有繁忙的循环......任何地方。

你甚至需要 buttonStartPressed 变量吗?为什么你有兴趣知道它是否被按下,当按钮被按下时执行一些动作不是主要思想吗?

您应该在 actionPerformed() 方法中创建 Task1,并根据您要执行的操作,启动一个执行该任务的线程(或者如果它真的很快就在 EDT 中运行它,所以它不会冻结 GUI)。

【讨论】:

    【解决方案2】:

    Swing(以及几乎每个 GUI 工具包)都有一个专用线程。这个线程,即事件调度线程,在您第一次需要时启动。这通常是当您 setVisibleJFrame 时。这个线程是一个巨大的循环,它的作用是消耗输入事件和重绘事件并相应地运行一些逻辑。

    在您的情况下,您实际上有两个线程。第一个是main 线程,第二个是EDT。您的main 线程正在执行busy wait。只要用户按下按钮,actionListener 的代码就会在EDT 中执行。

    您正在使用布尔变量作为使两个线程进行通信的一种方式。使用一些共享内存确实是inter-thread communication 的一种可能方式。

    现在,正如您所怀疑的那样,您应该避免进行繁忙的等待。它无谓地消耗CPU时间,每次唤醒都会打扰其他线程,并且不可避免地会有反应延迟。

    使用共享内存进行通信通常也很糟糕。这是一种太低级的交流方式,而且经常做错。从两个线程访问一条数据必须受到锁定机制的保护。即使是像布尔值这样简单的数据也会咬你,因为不能保证如果一个线程写入它,另一个线程会看到修改。在您的示例中,布尔值至少应声明为 volatile 以获得此保证。

    因此,添加volatile 关键字,您的解决方案就可以工作:您有一个EDT,他正在愉快地做自己的事情,当用户单击按钮时,main 线程将执行Task1。首先要问自己的问题是:Task1 是一项耗时的任务吗?实际上,最简单的解决方案是在EDT 中运行Task1,方法是从actionListener 调用它。请注意,在EDT 中执行某些代码会冻结 GUI。如果Task1 的持续时间少于 100 毫秒,用户甚至不会注意到冻结,在另一个线程中执行它也没有任何意义。如果您担心将 GUI 类与“任务”类耦合,那么您应该只使用观察者模式来防止直接依赖。

    如果任务很耗时并且您不希望 GUI 冻结,那么您应该使用多个线程。一种解决方案是您实施的解决方案。它有点受限,因为您只有一个main 线程,但它可以工作。您现在的问题是使这些线程进行通信。线程间通信中一个非常常见的模式是使用blocking queue。这也是一个共享的数据,但它被设计为由多个线程使用。一个线程 (EDT) 写入它 (add()),另一个从它读取 (take()) 并阻塞直到有东西被写入。对于您的简单示例,这似乎有点过头了,但它是在线程之间共享数据的一种非常方便的方式。写入阻塞队列的对象可以是任何东西;例如,它们可以表示要执行的命令。

    从 GUI 执行耗时功能的更传统方法是在需要时创建或使用专用线程。这可以使用低级 API (Thread) 或使用高级 API (ExecutorService) 来完成,两者都非常易于使用。同样,如果您想将 GUI 操作与线程创建分离,请使用观察者模式。

    如果这堵文字墙没有为您的问题提供简单的答案,我深表歉意,但是当我们混合 GUI 和线程时,有很多事情需要考虑。我希望它对您有所帮助,了解您的其他选择是什么。

    【讨论】:

    • 非常感谢。我非常感谢这个了不起的解释!!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-30
    相关资源
    最近更新 更多