JSeparator类 JPopupMenu类

本文深入探讨了Java Swing中的JSeparator和JPopupMenu组件,详细介绍了如何创建和使用它们,包括添加菜单项、分隔符、以及如何自定义外观。此外,文章还解释了如何显示和管理弹出菜单,以及如何使用特定的事件监听器来监听菜单的可见性和关闭状态。

6.1.6 JSeparator类

JSeparator类是一种特殊的组件,他在JMenu上提供分隔符。JPopupMenu与JToolBar类也支持分隔,但是每一个都使用JSeparator类的相应子类。除了可以放置在菜单上以外,JSeparator类也可以放置在任何我们希望使用水平或是垂直线来分隔屏幕不同区域的地方。

JSeparator是一个严格的可视化组件,所以,他没有数据模型。

创建JSeparator组件

要为菜单创建一个分隔,我们并不直接创建一个JSeparator,尽管我们可以这样做。相反,我们调用JMenu的addSeparator()方法,而菜单会创建分隔符并将其添加为下一个菜单项。他是一个JSeparator(不是JMenuItem子类)的事实是隐藏的。JMenu还有一个insertSeparator(int index)方法,这个方法可以使得我们在菜单上指定的位置添加分隔,这并不必须是下一个位置。

如果我们希望在菜单以外使用JSeparator(例如,在布局中分隔两个面板),我们应该使用JSeparator的两个构造函数:

public JSeparator()
JSeparator jSeparator = new JSeparator();
public JSeparator(int orientation)
JSeparator jSeparator = new JSeparator(JSeparator.VERTICAL);

 

这两个构造函数使得我们可以创建一个水平或是垂直分隔。如果没有指定方向,则为水平方向。如果我们希望显示式指定方向,我们可以使用JSeparator的常量HORIZONTAL或是VERTICAL。

JSeparator属性

在我们拥有JSeparator以外,我们就可以像其他的组件一样将其添加到屏幕中。组件的初始维度是空的(宽度与高度均为0),所以如果屏幕的布局管理器询问组件的尺寸应是多少,分隔符将会回复他不需要空间。另一方面,如果布局管理器提供一定量的空间,如果方向合适则分隔就会使用这个空间。例如,将一个水平JSeparator添加到BorderLayout面板的北侧则会在屏幕上绘制一个分隔线。然而,如果将水平JSeparator添加到相同面板的东侧则不会绘制任何内容。对于垂直JSeparator,其行为则是相反的:北侧将是空的,而在东侧则会出现垂直线。

表6-7显示了JSeparator的四个属性。

JSeparator属性

属性名 
数据类型 

访问性

accessibleContext 
AccessibleContext 

只读

orientation 
int 

读写绑定

UI 
SeparatorUI 

读写绑定

UIClassID 
String 

只读

自定义JSeparator观感

预安装的观感类型集合下的JSeparator外观以及其他的菜单组件显示在图6-3中。

表6-8列出了JSeparator的UIResource相关属性集合。对于JSeparator组件,有五个不同的属性。

JSeparator UIResource元素

属性字符串 

对象类型

Separator.background 

Color

Separator.foreground 

Color

Separator.insets 

Insets

Separator.thickness 

Integer

SeparatorUI 

String

6.1.7 JPopupMenu类

JPopupMenu组件是弹出菜单组件的容器,可以显示在任何地方并且为JMenu所支持。当一个编程者定义的触发事件发生时,我们显示JPopupMenu,并且菜单显示所包含的菜单组件。与JMenuBar类似,JpopupMenu使用SingleSelectionModel来管理当前被选中的元素。

创建JpopupMenu组件

JPopupMenu有两个构造函数:

public JPopupMenu()
JPopupMenu jPopupMenu = new JPopupMenu();
public JPopupMenu(String title)
JPopupMenu jPopupMenu = new JPopupMenu("Welcome");

 

如果需要,只有一个函数允许我们初始化菜单标题。标题的处理方式会依赖于所安装的观感。当前安装的观感会忽略标题。

向JPopupMenu添加菜项

与JMenu类似,一旦我们有了JPopupMenu,我们需要向其添加菜单项;否则,菜单将会是空的。有三个JPopupMenu方法可以添加菜单项,一个用于添加分隔符。

public JMenuItem add(JMenuItem menuItem);
public JMenuItem add(String label);
public JMenuItem add(Action action);
public void addSeparator();

 

另外还有一个由Container所继承的add()方法可以用于添加通常的AWT组件:

public Component add(Component component);

添加菜单项的通常方法是使用第一个add()方法。我们独立于弹出菜单创建菜单项,包含其行为行定,然后将其关联到菜单。使用第二个add()方法,我们必须将事件处理器关联到由方法返回的菜单;否则,当被选中时菜单并不会响应。下面的代码显示了两种方法。我们使用哪一种方法完全依赖于我们的喜好。可视化编程环境,例如JBuilder,会使用第一种。因为第一种方法并不是十分复杂,如果不是全部,绝大多数的程序员应该使用第一种方法。

JPopupMenu popupenu = new JPopupMenu();
ActionListener anActionListener = ...;
// The first way
JMenuItem firstItem = new JMenuItem("Hello");
firstItem.addActionListener(anActionListener);
popupMenu.add(firstItem);
// The second way
JMenuItem secondItem = popupMenu.add("World");
secondItem.addActionListener(anActionListener);

 

使用Action来创建与JPopupMenu结合使用的菜单项的方式类似于JMenu。然而,依据JPopupMenu类的Javadoc,并不鼓励使用add()方法的Action变体。相反,可以将Action传递给JMenuItem的构造函数,或者是使用setAction()方法进行配置,然后将其添加到JPopupMenu。为什么这个方法没有被deprecated并不清楚。

最后,我们可以使用addSeparator()方法添加分隔。

除了在菜单尾部添加菜单项,我们可以在指定的位置添加菜单项,或者是在指定的位置添加分隔。

public JMenuItem insert(Component component, int position);
public JMenuItem insert(Action action, int position);

与JMenu不同,并不存在insertSeparator()方法。但是我们可以使用由Container继承的add(Component component, int position)方法。如果我们希望移除组件,可以使用JPopupMenu特定的remove(Component component)方法。

显示JPopupMenu

与JMenu不同,简单的组装弹出菜单并不够。我们需要将弹出菜单与合适的组件相关联。在Swing 5.0版本之前,我们需要添加事件处理代码来触发弹出菜单的显示。现在,我们所需要做的就是为我们希望关联弹出菜单的组件调用setComponentPopupMenu()方法。当平台特定的触发事件发生时,弹出菜单会自动显示。

我们只需要简单的创建JPopupMenu的实例,并将其关联到我们希望显示弹出菜单的组件,如下所示:

JPopupMenu popupMenu = ...;
aComponent.setComponentPopupMenu(popupMenu);

对于弹出菜单比较重要的JComponent方法主要有getComponentPopupMenu(), setComponentPopupMenu(), getInheritsPopupMenu(), setInheritsPopupMenu()以及getPopupLocation()方法。setInheritsPopupMenu()方法会接受一个boolean参数。当为true时,并没有直接为组件设置组件弹出菜单,则会查找父容器用于弹出菜单。

JPopupMenu属性

表6-9列出了JPopupMenu的16个属性。更多的属性是由JComponent,Container与Component继承的。

JPopupMenu属性

 

属性名

数据类型

访问性

accessibleContext

AccessibleContext

只读

borderPainted

boolean

读写

component

Component

只读

invoker

Component

只读

label

String

读写绑定

lightWeightPopupEnabled

boolean

读写

margin

Insets

只读

menuKeyListeners

MenuKeyListener[]

只读

popupMenuListeners

PopupMenuListener[]

只读

popupSize

Dimension

只写

selected

Component

只写

selectionModel

SingleSelectionModel

只写

subElements

MenuElement[]

只读

UI

PopupMenuUI

读写绑定

UIClassID

String

只读

visible

boolean

读写

JPopupMenu最有趣的属性就是lightWeightPopupEnabled。通常来说,JPopupMenu会尝试避免为显示其菜单项而创建新的重量级组件。相反,当JPopupMenu可以完整的显示在最外层的窗体框架内时弹出菜单使用JPanel。否则,如果菜单项不适合时,JPopupMenu使用JWindow。然而,如果我们在不同的窗体层上混合使用轻量级与重量级组件,在一个JPanel内显示弹出菜单并不会起作用,因为在菜单层显示的一个重量级组件会在JPanel之前出现。要纠正这种行为,弹出菜单使用Panel用来显示菜单选项。默认情况下,JPopupMenu绝不使用Panel。

如果我们需要允许Panel显示,我们可以在单个的JPopupMenu级别或是整个的Applet或是程序进行配置。在单独的弹出级别,只需要将lightWeightPopupEnable属性设置为false。在系统级别,可以通过如下的代码进行设置:

// From now on, all JPopupMenus will be heavyweight
JPopupMenu.setDefaultLightWeightPopupEnabled(false);

这个方法必须在创建弹出菜单之前调用。JPopupMenu对象会在修改具有原始值(默认为true)之前创建。

监视弹出菜单可见性

类似于JMenu,JPopupMenu具有一个特殊的事件/监听器组合来监听弹出菜单何时可见,何时不可见或是何时关闭。这个组合中的事件就是PopupMenuEvent,而监听器就是PopupMenuListener。事件类只是简单的引用事件的源弹出菜单。

public class PopupMenuEvent extends EventObject {
  public PopupMenuEvent(Object source);
}

当JPopupMenu触发事件时,所注册的PopupMenuListener对象会通过他的三个接口方法得到通知。这可以使得我们依据系统状态或是弹出菜单的调用是谁来自定义当前的菜单项。PopupMenuListener接口定义如下:

public interface PopupMenuListener extends EventListener {
  public void popupMenuCanceled(PopupMenuEvent e);
  public void popupMenuWillBecomeInvisible(PopupMenuEvent e);
  public void popupMenuWillBecomeVisible(PopupMenuEvent e);
}

 

自定义JPopupMenu观感

每一个所安装的Swing观感都会提供不同的JPopupMenu外观与一个默认的UIResource值集合。图6-6显示了预安装的观感类型集合:Motif,Windows,Ocean的JPopupMenu组件外观。注意,在预安装的观感类中,只有Motif使用JPopupMenu的title属性。

表6-10显示了JPopupMenu相关的UIResource属性。对于JPopupMenu组件,有五个不同的属性。

JPopupMenu UIResource元素

 

属性字符串

对象类型

PopupMenu.actionMap

ActionMap

PopupMenu.background

Color

PopuMenu.border

Border

PopupMenu.consumeEventOnClose

Boolean

PopupMenu.font

Font

PopupMenu.foreground

Color

PopupMenu.popupSound

String

PopupMenu.selectedWindowInputMapBindings

Object[]

PopupMenu.selectedWindowInputMapBindings.RightToLeft

Object[]

PopupMenuSeparatorUI

String

PopupMenuUI

String

JPopupMenu.Separator类

JPopupMenu类维护了其自己的分隔符从而允许自定义JPopupMenu上的分隔符的观感。这个自定义的分隔符是JPopupMenu的内联类。

当我们调用JPopupMenu的addSeparator()方法时,则会自动生成这个类的一个实例并添加到弹出菜单中。另外,我们也可以通过调用无参数的构造函数来创建这个分隔符:

JSeparator popupSeparator =new JPopupMenu.Separator();

这两种都会创建水平分隔符。

注意:如果我们要修改分隔符的方向,我们必须使用JPopupMenu.Separator.VERTICAL作为参数调用由JSeparator所继承的setOrientation()方法。然而在弹出菜单中具有一个垂直分隔符并不合适。

一个完整的弹出菜单使用示例

列表6-3中的程序将JPopupMenu使用的所有方面组合在一起,包括监听所有菜单项的选中,同时监听菜单何时显示。程序的输出如图6-7所示。

swing_6_7

 

/**
 * 
 */
package net.ariel.ch06;
 
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
 
/**
 * @author mylxiaoyi
 *
 */
public class PopupSample {
 
	// Define ActionListener
	static class PopupActionListener implements ActionListener {
		public void actionPerformed(ActionEvent event) {
			System.out.println("Selected: "+event.getActionCommand());
		}
	}
 
	// Define PopupMenuListener
	static class MyPopupMenuListener implements PopupMenuListener {
		public void popupMenuCanceled(PopupMenuEvent event) {
			System.out.println("Canceled");
		}
		public void popupMenuWillBecomeInvisible(PopupMenuEvent event) {
			System.out.println("Becoming Invisible");
		}
		public void popupMenuWillBecomeVisible(PopupMenuEvent event) {
			System.out.println("Becoming Visible");
		}
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
 
		Runnable runner = new Runnable() {
			public void run() {
				// Create frame
				JFrame frame = new JFrame("PopupMenu Sample");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
				ActionListener acitonListener = new PopupActionListener();
				PopupMenuListener popupMenuListener = new MyPopupMenuListener();
 
				// Create popup menu, attach popup menu listener
				JPopupMenu popupMenu = new JPopupMenu("Title");
				popupMenu.addPopupMenuListener(popupMenuListener);
 
				// Cut
				JMenuItem cutMenuItem = new JMenuItem("Cut");
				cutMenuItem.addActionListener(acitonListener);
				popupMenu.add(cutMenuItem);
 
				// Copy
				JMenuItem copyMenuItem = new JMenuItem("Copy");
				copyMenuItem.addActionListener(acitonListener);
				popupMenu.add(copyMenuItem);
 
				// Paste
				JMenuItem pasteMenuItem = new JMenuItem("Paste");
				pasteMenuItem.addActionListener(acitonListener);
				popupMenu.add(pasteMenuItem);
 
				// Separator
				popupMenu.addSeparator();
 
				// Find
				JMenuItem findMenuItem = new JMenuItem("Find");
				findMenuItem.addActionListener(acitonListener);
				popupMenu.add(findMenuItem);
 
				JButton label = new JButton();
				frame.add(label);
				label.setComponentPopupMenu(popupMenu);
 
				frame.setSize(350, 250);
				frame.setVisible(true);
			}
		};
 
		EventQueue.invokeLater(runner);
	}
 
}
当然可以,以下是**修改后的完整 `TemperatureMonitorGUI` 代码**,我已经在其中加入了对 **阶段标签(“阶段 1:”、“阶段 2:”、“阶段 3:”)的可见性控制逻辑**,确保你在点击 "+" 或 "-" 按钮时,阶段名称也能正确显示或隐藏。 --- ## ✅ 修改说明 - 新增字段:`private JLabel[] stageLabels = new JLabel[3];` - 在初始化阶段面板时保存每个 `JLabel` 的引用 - 在按钮事件中通过该数组控制可见性 --- ## ✅ 修改后的完整 `TemperatureMonitorGUI.java` ```java package demo1; import com.fazecast.jSerialComm.SerialPort; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.util.Arrays; public class TemperatureMonitorGUI extends JFrame { private JLabel temperatureLabel; private JLabel statusLabel; private JButton connectButton; private JButton readButton; private JButton startControlButton; private JButton refreshPortButton; private JComboBox<String> portComboBox; private JComboBox<String> baudRateComboBox; private JComboBox<String> dateBiteComboBox; private JComboBox<String> stopBitsComboBox; private JComboBox<String> parityComboBox; private JComboBox<String> modeComboBox; private TemperatureController controller; // 输入组件 private JTextField transitionTimeField; private JTextField constantTempField; private Map<String, Component> modeComponents = new HashMap<>(); // 曲线模式字段 private JTextField[] curveTempFields = new JTextField[3]; private JTextField[] curveDurationFields = new JTextField[3]; private JTextField[] curveHoldFields = new JTextField[3]; private JTextField finalTempField; private JTextField finalTransitionField; // 阶段标签 private JLabel[] stageLabels = new JLabel[3]; // 新增字段:保存阶段标签 public TemperatureMonitorGUI() { setTitle("MODBUS RTU 温控系统"); setSize(1100, 700); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getContentPane().setBackground(new Color(240, 245, 250)); JPanel mainPanel = new JPanel(new BorderLayout(15, 15)); mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); add(mainPanel); JPanel topPanel = createTopPanel(); mainPanel.add(topPanel, BorderLayout.NORTH); JPanel centerPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(8, 8, 8, 8); gbc.fill = GridBagConstraints.BOTH; JPanel inputPanel = createInputPanel(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.3; gbc.weighty = 1.0; centerPanel.add(inputPanel, gbc); JPanel buttonPanel = createButtonPanel(); gbc.gridx = 1; gbc.weightx = 0.2; centerPanel.add(buttonPanel, gbc); JPanel rightPanel = createRightPanel(); gbc.gridx = 2; gbc.weightx = 0.4; centerPanel.add(rightPanel, gbc); mainPanel.add(centerPanel, BorderLayout.CENTER); JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); bottomPanel.setBorder(BorderFactory.createEtchedBorder()); bottomPanel.setBackground(new Color(230, 240, 255)); statusLabel = new JLabel("状态: 未连接"); bottomPanel.add(statusLabel); mainPanel.add(bottomPanel, BorderLayout.SOUTH); } private JPanel createTopPanel() { JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); topPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 15, 0)); topPanel.setBackground(new Color(70, 130, 180)); temperatureLabel = new JLabel("当前温度: -- °C"); temperatureLabel.setFont(new Font("微软雅黑", Font.BOLD, 28)); temperatureLabel.setForeground(Color.WHITE); topPanel.add(temperatureLabel); return topPanel; } private JPanel createInputPanel() { JPanel inputPanel = new JPanel(new GridBagLayout()); inputPanel.setBorder(BorderFactory.createTitledBorder("通信配置")); inputPanel.setBackground(Color.WHITE); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 5, 5, 5); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; int row = 0; gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("串口:"), gbc); gbc.gridx = 1; JPanel portPanel = new JPanel(new BorderLayout(5, 0)); portComboBox = new JComboBox<>(); portComboBox.setPreferredSize(new Dimension(180, 25)); refreshPortList(); portPanel.add(portComboBox, BorderLayout.CENTER); refreshPortButton = new JButton("刷新"); refreshPortButton.addActionListener(e -> refreshPortList()); portPanel.add(refreshPortButton, BorderLayout.EAST); inputPanel.add(portPanel, gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("波特率:"), gbc); gbc.gridx = 1; baudRateComboBox = new JComboBox<>(new String[]{"1200", "2400", "4800", "9600","19200"}); baudRateComboBox.setSelectedIndex(0); inputPanel.add(baudRateComboBox, gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("数据位"), gbc); gbc.gridx = 1; dateBiteComboBox = new JComboBox<>(new String[]{"6", "7", "8"}); dateBiteComboBox.setSelectedIndex(0); inputPanel.add(dateBiteComboBox, gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("停止位:"), gbc); gbc.gridx = 1; stopBitsComboBox = new JComboBox<>(new String[]{"1", "1.5", "2"}); stopBitsComboBox.setSelectedIndex(0); inputPanel.add(stopBitsComboBox, gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("校验方式:"), gbc); gbc.gridx = 1; parityComboBox = new JComboBox<>(new String[]{"None", "Even", "Odd"}); parityComboBox.setSelectedIndex(0); inputPanel.add(parityComboBox, gbc); gbc.gridx = 0; gbc.gridy = row++; gbc.gridwidth = 2; inputPanel.add(new JSeparator(), gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("初始温度:"), gbc); gbc.gridx = 1; JButton queryCurrentTempButton = new JButton("查询"); queryCurrentTempButton.setPreferredSize(new Dimension(80, 25)); queryCurrentTempButton.addActionListener(this::queryCurrentTemperature); inputPanel.add(queryCurrentTempButton, gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("设定温度:"), gbc); gbc.gridx = 1; JButton querySetTempButton = new JButton("查询"); querySetTempButton.setPreferredSize(new Dimension(80, 25)); querySetTempButton.addActionListener(this::querySetTemperature); inputPanel.add(querySetTempButton, gbc); gbc.gridx = 0; gbc.gridy = row++; gbc.gridwidth = 2; inputPanel.add(new JSeparator(), gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("模式选择:"), gbc); gbc.gridx = 1; modeComboBox = new JComboBox<>(new String[]{"定值模式", "曲线模式"}); modeComboBox.setSelectedIndex(0); inputPanel.add(modeComboBox, gbc); gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("启动控制:"), gbc); gbc.gridx = 1; startControlButton = new JButton("启动"); startControlButton.setPreferredSize(new Dimension(100, 30)); startControlButton.setBackground(new Color(50, 150, 50)); startControlButton.setForeground(Color.WHITE); startControlButton.addActionListener(this::startControl); inputPanel.add(startControlButton, gbc); return inputPanel; } private JPanel createButtonPanel() { JPanel buttonPanel = new JPanel(new GridBagLayout()); buttonPanel.setBorder(BorderFactory.createTitledBorder("操作")); buttonPanel.setBackground(Color.WHITE); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(15, 10, 15, 10); gbc.fill = GridBagConstraints.HORIZONTAL; connectButton = new JButton("连接串口"); connectButton.setPreferredSize(new Dimension(150, 40)); connectButton.setFont(new Font("微软雅黑", Font.BOLD, 14)); connectButton.setBackground(new Color(70, 130, 180)); connectButton.setForeground(Color.WHITE); connectButton.addActionListener(this::connectSerialPort); gbc.gridy = 0; buttonPanel.add(connectButton, gbc); readButton = new JButton("读取温度"); readButton.setPreferredSize(new Dimension(150, 40)); readButton.setFont(new Font("微软雅黑", Font.BOLD, 14)); readButton.setBackground(new Color(60, 179, 113)); readButton.setForeground(Color.WHITE); readButton.addActionListener(this::readTemperature); gbc.gridy = 1; buttonPanel.add(readButton, gbc); return buttonPanel; } private JPanel createRightPanel() { JPanel rightPanel = new JPanel(new BorderLayout()); rightPanel.setBorder(BorderFactory.createTitledBorder("模式配置")); rightPanel.setBackground(Color.WHITE); JPanel constantModePanel = createConstantModePanel(); modeComponents.put("定值模式", constantModePanel); JPanel curveModePanel = createCurveModePanel(); modeComponents.put("曲线模式", curveModePanel); rightPanel.add(modeComponents.get("定值模式"), BorderLayout.CENTER); modeComboBox.addActionListener(e -> { String selectedMode = (String) modeComboBox.getSelectedItem(); rightPanel.removeAll(); rightPanel.add(modeComponents.get(selectedMode), BorderLayout.CENTER); rightPanel.revalidate(); rightPanel.repaint(); }); return rightPanel; } private JPanel createConstantModePanel() { JPanel panel = new JPanel(new GridBagLayout()); panel.setBackground(Color.WHITE); panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(10, 10, 10, 10); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridx = 0; gbc.gridy = 0; panel.add(new JLabel("目标温度 (°C):"), gbc); gbc.gridx = 1; constantTempField = new JTextField(10); constantTempField.setText("25.5"); panel.add(constantTempField, gbc); gbc.gridx = 0; gbc.gridy = 1; panel.add(new JLabel("过渡时间 (秒):"), gbc); gbc.gridx = 1; transitionTimeField = new JTextField(10); transitionTimeField.setText("30"); panel.add(transitionTimeField, gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; JLabel infoLabel = new JLabel("<html><div style='width:250px;'>定值模式将温度稳定在设定值,过渡时间表示达到目标温度所需时间</div></html>"); infoLabel.setForeground(new Color(100, 100, 100)); panel.add(infoLabel, gbc); return panel; } private JPanel createCurveModePanel() { JPanel panel = new JPanel(new GridBagLayout()); panel.setBackground(Color.WHITE); panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(10, 10, 10, 10); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; panel.add(new JLabel("阶段"), gbc); gbc.gridx = 1; panel.add(new JLabel("目标温度 (°C)"), gbc); gbc.gridx = 2; panel.add(new JLabel("过渡时间 (秒)"), gbc); gbc.gridx = 3; panel.add(new JLabel("保持时间 (秒)"), gbc); for (int i = 0; i < 3; i++) { gbc.gridx = 0; gbc.gridy = i + 1; gbc.anchor = GridBagConstraints.WEST; JLabel stageLabel = new JLabel("阶段 " + (i + 1) + ":"); stageLabels[i] = stageLabel; // 保存引用 panel.add(stageLabel, gbc); gbc.gridx = 1; curveTempFields[i] = new JTextField("0.0"); curveTempFields[i].setColumns(8); panel.add(curveTempFields[i], gbc); gbc.gridx = 2; curveDurationFields[i] = new JTextField("30"); curveDurationFields[i].setColumns(8); panel.add(curveDurationFields[i], gbc); gbc.gridx = 3; curveHoldFields[i] = new JTextField("60"); curveHoldFields[i].setColumns(8); panel.add(curveHoldFields[i], gbc); if (i > 0) { stageLabel.setVisible(false); curveTempFields[i].setVisible(false); curveDurationFields[i].setVisible(false); curveHoldFields[i].setVisible(false); } } gbc.gridy = 4; gbc.gridx = 0; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.CENTER; JPanel controlButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 0)); JButton addStageButton = new JButton("+"); JButton removeStageButton = new JButton("-"); controlButtonPanel.add(addStageButton); controlButtonPanel.add(removeStageButton); panel.add(controlButtonPanel, gbc); gbc.gridy = 5; gbc.gridx = 0; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.WEST; panel.add(new JLabel("最终目标:"), gbc); gbc.gridx = 1; finalTempField = new JTextField("30.0", 8); panel.add(finalTempField, gbc); gbc.gridx = 2; finalTransitionField = new JTextField("30", 8); panel.add(finalTransitionField, gbc); gbc.gridy = 6; gbc.gridx = 0; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.WEST; JLabel infoLabel = new JLabel("<html><div style='width:350px;'>点击 '+' 增加阶段,'-' 减少阶段。未填写或为 0 的阶段将被跳过</div></html>"); infoLabel.setForeground(new Color(100, 100, 100)); panel.add(infoLabel, gbc); final int[] maxVisibleStages = {1}; addStageButton.addActionListener(e -> { if (maxVisibleStages[0] < 3) { maxVisibleStages[0]++; for (int i = 0; i < 3; i++) { boolean visible = i < maxVisibleStages[0]; stageLabels[i].setVisible(visible); // 使用引用设置可见性 curveTempFields[i].setVisible(visible); curveDurationFields[i].setVisible(visible); curveHoldFields[i].setVisible(visible); } panel.revalidate(); panel.repaint(); } }); removeStageButton.addActionListener(e -> { if (maxVisibleStages[0] > 1) { maxVisibleStages[0]--; for (int i = 0; i < 3; i++) { boolean visible = i < maxVisibleStages[0]; stageLabels[i].setVisible(visible); curveTempFields[i].setVisible(visible); curveDurationFields[i].setVisible(visible); curveHoldFields[i].setVisible(visible); } panel.revalidate(); panel.repaint(); } }); return panel; } private void connectSerialPort(ActionEvent e) { String selectedPort = (String) portComboBox.getSelectedItem(); if (selectedPort != null && controller == null) { int baudRate = Integer.parseInt((String) baudRateComboBox.getSelectedItem()); int dateBite = Integer.parseInt((String) dateBiteComboBox.getSelectedItem()); int stopBits = getStopBitsValue(); int parity = getParityValue(); controller = new TemperatureController(this, selectedPort, baudRate, dateBite, stopBits, parity); controller.start(); connectButton.setText("断开连接"); connectButton.setBackground(new Color(246, 115, 115)); } else if (controller != null) { controller.stop(); controller = null; setStatus("已断开连接"); connectButton.setText("连接串口"); connectButton.setBackground(new Color(70, 130, 180)); } } private int getStopBitsValue() { String stopBits = (String) stopBitsComboBox.getSelectedItem(); switch (stopBits) { case "1": return SerialPort.ONE_STOP_BIT; case "1.5": return SerialPort.ONE_POINT_FIVE_STOP_BITS; case "2": return SerialPort.TWO_STOP_BITS; default: return SerialPort.ONE_STOP_BIT; } } private int getParityValue() { String parity = (String) parityComboBox.getSelectedItem(); switch (parity) { case "None": return SerialPort.NO_PARITY; case "Even": return SerialPort.EVEN_PARITY; case "Odd": return SerialPort.ODD_PARITY; default: return SerialPort.NO_PARITY; } } private void readTemperature(ActionEvent e) { if (controller != null) { try { byte slaveId = 0x01; controller.readActualTemperature(slaveId); } catch (Exception ex) { showError("读取温度时发生错误: " + ex.getMessage()); } } else { showError("请先连接串口"); } } private void queryCurrentTemperature(ActionEvent e) { if (controller != null) { try { byte slaveId = 0x01; controller.readActualTemperature(slaveId); } catch (Exception ex) { showError("查询当前温度时发生错误: " + ex.getMessage()); } } else { showError("请先连接串口"); } } private void querySetTemperature(ActionEvent e) { if (controller != null) { try { byte slaveId = 0x01; controller.readSetTemperature(slaveId); } catch (Exception ex) { showError("查询设定温度时发生错误: " + ex.getMessage()); } } else { showError("请先连接串口"); } } private void startControl(ActionEvent e) { String selectedMode = (String) modeComboBox.getSelectedItem(); if ("恒温模式".equals(selectedMode)) { try { float targetTemp = Float.parseFloat(constantTempField.getText()); controller.setConstantTemperature(targetTemp); showInfo("恒温模式启动: " + targetTemp + "°C"); } catch (NumberFormatException ex) { showError("请输入有效的温度值"); } } else if ("曲线模式".equals(selectedMode)) { float[] temps = new float[4]; int[] durations = new int[4]; int[] holdTimes = new int[3]; int stageCount = 0; for (int i = 0; i < 3; i++) { try { float temp = Float.parseFloat(curveTempFields[i].getText()); int duration = Integer.parseInt(curveDurationFields[i].getText()); int holdTime = Integer.parseInt(curveHoldFields[i].getText()); if (temp >= 0 && duration > 0 && holdTime >= 0) { temps[stageCount] = temp; durations[stageCount] = duration; holdTimes[stageCount] = holdTime; stageCount++; } } catch (NumberFormatException ignored) {} } try { temps[stageCount] = Float.parseFloat(finalTempField.getText()); durations[stageCount] = Integer.parseInt(finalTransitionField.getText()); } catch (NumberFormatException ex) { showError("请正确输入最终目标温度和过渡时间"); return; } if (stageCount == 0) { showError("至少需要一个有效阶段"); return; } controller.profileManager.startCurveControl( Arrays.copyOf(temps, stageCount + 1), Arrays.copyOf(durations, stageCount + 1), Arrays.copyOf(holdTimes, stageCount) ); showInfo("曲线模式启动: " + stageCount + "个阶段 + 最终目标"); } } public void refreshPortList() { portComboBox.removeAllItems(); for (SerialPort port : SerialPort.getCommPorts()) { portComboBox.addItem(port.getSystemPortName()); } } public void updateTemperature(double temperature) { SwingUtilities.invokeLater(() -> { temperatureLabel.setText(String.format("当前温度: %.1f°C", temperature)); if (temperature > 35) { temperatureLabel.setForeground(new Color(220, 20, 60)); } else if (temperature < 10) { temperatureLabel.setForeground(new Color(30, 144, 255)); } else { temperatureLabel.setForeground(Color.WHITE); } }); } public void setStatus(String status) { SwingUtilities.invokeLater(() -> statusLabel.setText("状态: " + status) ); } private void showError(String message) { SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, message, "错误", JOptionPane.ERROR_MESSAGE) ); } private void showInfo(String message) { SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, message, "信息", JOptionPane.INFORMATION_MESSAGE) ); } } ``` --- ## ✅ 运行效果验证 | 操作 | 现象 | |------------------|------------------------------------| | 默认只显示阶段1 | “阶段1:” 显示,“阶段2/3”隐藏 | | 点击"+" | “阶段2:”显示 | | 再点"+" | “阶段3:”显示 | | 点击"-" | 阶段数减少,对应 label 也被隐藏 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值