为JAVA添加信号/槽支持(一)
2012-09-01 21:50
441 查看
QT语言以信号/槽方式取代callback机制,为编程提供了灵活性,而备受赞誉。本文将介绍如何为JAVA添加信号/槽机制的支持,并给出一个有趣的使用信号/槽机制的例子。
在QT中以connect函数注册信号/槽,使用emit函数发送信号,在我们的程序中也使用这两个函数进行信号/槽注册和信号的发送。创建类SignalSlotHandle类,内容如下:
在信号/槽处理类中,使用辅助类ConnectInfo,一个ConnectInfo类对象表示一个信号/槽注册信息。connect信号/槽注册函数的实现很简单,其功能就是向同步列表中添加ConnectInfo对象。emit信号发送函数,在外部调用时可以认为是信号发送,但其内部实现却是执行信号相应处理函数。就是这个100行左右代码的类便可以在你的工程中添加信号/槽机制支持。下面是使用这个类实现的一个有趣的例子,先来看看运行情况。
在这个例子中,模拟了一出舞台场景,场景中有4个演员A(饰演皇帝)、B(饰演忠臣1)、C(饰演忠臣2)、D(饰演奸臣)。场景的启动是由导演的一声Action开始(在程序由点击Action按钮实现),皇帝首先发出命令要求忠臣2自刎,忠臣2在2秒钟后显示出手足无措,忠臣1在4秒钟后对皇帝进行劝说,奸臣在8秒钟后对皇帝进行火上浇油的劝说,而后皇帝大怒要求他们闭嘴,然后在沉默10秒钟后皇帝发出新的命令让忠臣2继续做他的宰相。在此期间,忠臣2根据忠臣1、奸臣和皇帝的态度作出不同的反应。测试类如下:
只需要关注这个类上部分,此类界面由Netbeans自动生成。使用信号/槽机制,首先是注册信号/槽对,在构造函数完成。
当点击Action按钮时,当前类发出"action"信号,调用相应槽函数aAct、bAct、cAct、dAct。首先皇帝角色发出忠臣2自刎的命令(aAct),其他角色分别等待设定时间。之后忠臣2角色表现无助(cAct);忠臣1角色进行劝说(bAct),发出"sayed"信号,忠臣2角色接收后决定进行辩解(cInRole);奸臣角色进行火上浇油的劝说(dAct),发出"sayed"信号,忠臣2角色接收后决定遵从命令自杀(cInRole)。在bAct、dAct槽函数有个计数,用于当忠臣1和奸臣都在说时,发出"bdsayed"信号给皇帝,皇帝角色命令他们闭嘴(发出信号"bdShutup"),忠臣1和奸臣收到信号后保持沉默(bInRole和dInRole),忠臣2收到信号后表现出忐忑不安(cInRole)。皇帝角色在大家沉默10秒后,发布新命令(信号"newcommond")赦免忠臣2。忠臣1、忠臣2、奸臣分别表现不同的反应(bInRole、cInRole、dInRole)。
在程序中有个boolean类型变量worried,用于控制忠臣2角色是否表现出忐忑不安,当worried为true时,界面上“辩解”和“遵从命令”单选按钮不停交替选中,以表现忐忑不安的状态。设置这个变量是因为每次函数调用都是在独立的线程中执行,第二次调用并不影响第一次调用,所以使用一个“全局”量控制忐忑不安状态时调用函数的结束。
问题:
1、 由于信号/槽处理类为静态类,从包加载起对象就已经存在,直到JVM销毁。所以同步列表需要实时维护,当注册的对象不存在时应该及时的清理列表,以减少资源占用。
2、 在emit函数中使用Thread类进行线程创建,存在与Swing图形组件的兼容性问题。
3、 例子程序在反复点击Action按钮时,会抛出InvocationTargetException异常和ClassCastException异常(java.lang.Character cannot be cast to javax.swing.Painter)。异常的发生通常在cInRole函数中的这个循环中。当减小此循环的持续时间时,异常出现的概率明显减小。
在QT中以connect函数注册信号/槽,使用emit函数发送信号,在我们的程序中也使用这两个函数进行信号/槽注册和信号的发送。创建类SignalSlotHandle类,内容如下:
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package cn.org.liuf.framework.signalmash; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author liuf */ public class SignalSlotHandle { /** * 同步列表,保存注册的信号/槽信息 */ static List<ConnectInfo> conns = Collections.synchronizedList(new ArrayList<ConnectInfo>()); /** * 同步函数,注册信号/槽 * @param o 信号发送对象 * @param signal 信号 * @param ot 信号接收对象 * @param slot 信号处理函数,使用字符串表示的函数名 * @param c_param 信号传递的参数类数组,必须是信号处理函数实际参数类型,且顺序必须一致 */ public static synchronized void connect(Object o, String signal, Object ot, String slot, Class<?>... c_param) { ConnectInfo ci = new ConnectInfo(); ci.setSignalObject(o); ci.setSlotObject(ot); ci.setSignal(signal); ci.setSlot(slot); ci.setParams(c_param); conns.add(ci); } /** * 同步函数,发送信号和参数 * @param o 发送信号对象 * @param signal 信号 * @param args 参数对象数组,允许参数个数大于注册时的参数个数,但参数类型必须与注册时保持一致,且个数不能小于注册时个数 */ public static synchronized void emit(Object o, String signal, Object... args) { final Object[] argst = args; for (ConnectInfo ci : conns) { if (ci.getSignalObject() == o) { if (ci.getSignal().compareTo(signal) == 0) { final ConnectInfo cit = ci; new Thread() { // 为使用所有处理信号的函数能够同时执行,使用线程方式调用 @Override public void run() { // 使用反射调用信号处理函数 try { Method method; method = cit.getSlotObject().getClass().getMethod(cit.getSlot(), cit.getParams()); int alen = argst.length; int plen = cit.getParams().length; Object[] t; if (alen > plen) { // 用于支持信号传递的参数个数大于实际参数个数,要求相应参数类型必须相同 t = Arrays.copyOf(argst, plen); } else { t = argst; } method.invoke(cit.getSlotObject(), t); } catch (IllegalAccessException ex) { Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalArgumentException ex) { Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex); } catch (InvocationTargetException ex) { Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchMethodException ex) { Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex); } } }.start(); } } } } } class ConnectInfo { Object signalObject; // 发送信号对象 Object slotObject; // 接受信号对象 String signal; // 信号 String slot; // 信号处理函数 Class<?>[] params; // 传递的参数类型数组 public Object getSignalObject() { return signalObject; } public void setSignalObject(Object signalObject) { this.signalObject = signalObject; } public Object getSlotObject() { return slotObject; } public void setSlotObject(Object slotObject) { this.slotObject = slotObject; } public String getSignal() { return signal; } public void setSignal(String signal) { this.signal = signal; } public String getSlot() { return slot; } public void setSlot(String slots) { this.slot = slots; } public Class<?>[] getParams() { return params; } public void setParams(Class<?>[] params) { this.params = params; } }
在信号/槽处理类中,使用辅助类ConnectInfo,一个ConnectInfo类对象表示一个信号/槽注册信息。connect信号/槽注册函数的实现很简单,其功能就是向同步列表中添加ConnectInfo对象。emit信号发送函数,在外部调用时可以认为是信号发送,但其内部实现却是执行信号相应处理函数。就是这个100行左右代码的类便可以在你的工程中添加信号/槽机制支持。下面是使用这个类实现的一个有趣的例子,先来看看运行情况。
在这个例子中,模拟了一出舞台场景,场景中有4个演员A(饰演皇帝)、B(饰演忠臣1)、C(饰演忠臣2)、D(饰演奸臣)。场景的启动是由导演的一声Action开始(在程序由点击Action按钮实现),皇帝首先发出命令要求忠臣2自刎,忠臣2在2秒钟后显示出手足无措,忠臣1在4秒钟后对皇帝进行劝说,奸臣在8秒钟后对皇帝进行火上浇油的劝说,而后皇帝大怒要求他们闭嘴,然后在沉默10秒钟后皇帝发出新的命令让忠臣2继续做他的宰相。在此期间,忠臣2根据忠臣1、奸臣和皇帝的态度作出不同的反应。测试类如下:
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package test.ui; import cn.org.liuf.framework.signalmash.SignalSlotHandle; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author liuf */ public class TestJFrame extends javax.swing.JFrame { private int sayed = 0; private boolean worried = true; /** * Creates new form TestJFrame */ public TestJFrame() { initComponents(); buttonGroup1.add(this.cExplainRadioButton); buttonGroup1.add(this.cKillRadioButton); SignalSlotHandle.connect(this, "action", this, "aAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "bAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "cAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "dAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "directAct", new Class[]{}); SignalSlotHandle.connect(this, "bdShutup", this, "bInRole", String.class); // 皇帝演员让忠臣1闭嘴, 将信号通过参数String传递,以便槽函数根据信号作出处理 SignalSlotHandle.connect(this, "bdShutup", this, "dInRole", String.class); // 皇帝演员让奸臣1闭嘴,String参数同上 SignalSlotHandle.connect(this, "bdShutup", this, "cInRole", String.class, Boolean.class); // 皇帝发火了,忠臣2忐忑中,String参数同上,boolean用于设置忐忑状态, ·始终是true(表示进入忐忑状态) SignalSlotHandle.connect(this.bjLabel, "sayed", this, "cInRole", String.class, Boolean.class); // 忠臣1演员劝说完, 忠臣2演员选择辨解,boolean类型变量用于标识忠臣1(true)和奸臣1(false),String参数同上 SignalSlotHandle.connect(this.djLabel, "sayed", this, "cInRole", String.class, Boolean.class); // 奸臣1演员火上浇油完,忠臣2选择遵从命令(自杀),String参数同上 SignalSlotHandle.connect(this, "bdsayed", this, "aInRole", String.class); // 忠臣1和奸臣1说完话后,皇帝大怒,发送“bdShutup"信号,String参数同上 SignalSlotHandle.connect(this, "newcommond", this, "bInRole", String.class); // 皇帝发布新的命令,不杀忠臣2 SignalSlotHandle.connect(this, "newcommond", this, "cInRole", String.class, Boolean.class); // boolean类型始终为false SignalSlotHandle.connect(this, "newcommond", this, "dInRole", String.class); } public void aInRole(String signal) { if (signal != null) { if (signal.compareTo("bdsayed") == 0) { this.aTextField.setText("你们闭嘴"); SignalSlotHandle.emit(this, "bdShutup", "bdShutup", true); this.directjTextArea.setText("皇帝发火了!!大家沉默中,\n10秒后皇帝发布新的命令"); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } this.aTextField.setText("忠臣2,你继续做你的宰相吧"); SignalSlotHandle.emit(this, "newcommond", "newcommond", false); this.directjTextArea.setText("场景剧本结束"); } } } public void bInRole(String signal) { if (signal != null) { if (signal.compareTo("bdShutup") == 0) { this.bTextField.setText("我沉默中..."); }else if(signal.compareTo("newcommond") == 0){ this.bTextField.setText("高兴中..."); } } } public void cInRole(String signal, Boolean isGood) { worried = isGood; if (signal == null) { this.cTextField.setText("不知道接收的是什么信号"); return; } if (signal.compareTo("sayed") == 0 && isGood) { this.cTextField.setText("我要为自己辨解"); this.cExplainRadioButton.setSelected(true); } else if (signal.compareTo("sayed") == 0 && !isGood) { this.cTextField.setText("皇帝下命令了,我就要死了"); this.cKillRadioButton.setSelected(true); } else if (signal.compareTo("bdShutup") == 0) { this.cTextField.setText("我十分的忐忑啊"); while (this.worried) { this.cExplainRadioButton.doClick(); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } this.cKillRadioButton.doClick(); } }else if(signal.compareTo("newcommond") == 0){ this.cTextField.setText("好险啊,还好皇帝放过了我"); this.cExplainRadioButton.setSelected(false); } } public void dInRole(String signal) { if (signal != null) { if (signal.compareTo("bdShutup") == 0) { this.dTextField.setText("我沉默中..."); }else if(signal.compareTo("newcommond") == 0){ this.dTextField.setText("郁闷中..."); } } } public void directAct() { this.directjTextArea.setText("皇帝发布命令,忠臣1 4秒\n后动作,忠臣2 2秒后动\n作,奸臣1 8秒后动作 "); } public void aAct() { this.aTextField.setText("忠臣2,你自刎吧"); } public void bAct() { this.bTextField.setText("开演了,进入角色"); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } this.bTextField.setText("劝说皇帝,不杀忠臣2"); SignalSlotHandle.emit(this.bjLabel, "sayed", "sayed", true); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } sayed++; if (sayed == 2) { SignalSlotHandle.emit(this, "bdsayed", "bdsayed"); sayed = 0; } } public void cAct() { this.cTextField.setText("开演了,进入角色"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } this.cTextField.setText("心理独白:我该怎么办"); } public void dAct() { this.dTextField.setText("开演了,进入角色"); try { TimeUnit.SECONDS.sleep(8); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } this.dTextField.setText("火上浇油,杀了忠臣2"); SignalSlotHandle.emit(this.djLabel, "sayed", "sayed", false); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } sayed++; if (sayed == 2) { SignalSlotHandle.emit(this, "bdsayed", "bdsayed"); sayed = 0; } } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { buttonGroup1 = new javax.swing.ButtonGroup(); actionButton = new javax.swing.JButton(); aTextField = new javax.swing.JTextField(); jLabel1 = new javax.swing.JLabel(); ajLabel = new javax.swing.JLabel(); bTextField = new javax.swing.JTextField(); bjLabel = new javax.swing.JLabel(); cjLabel = new javax.swing.JLabel(); cExplainRadioButton = new javax.swing.JRadioButton(); cKillRadioButton = new javax.swing.JRadioButton(); dTextField = new javax.swing.JTextField(); djLabel = new javax.swing.JLabel(); cTextField = new javax.swing.JTextField(); jScrollPane1 = new javax.swing.JScrollPane(); directjTextArea = new javax.swing.JTextArea(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); actionButton.setText("Action"); actionButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { actionButtonMouseClicked(evt); } }); aTextField.setText("我要开演"); jLabel1.setText("导演"); ajLabel.setText("演员A(皇帝)"); bjLabel.setText("演员B(忠臣1)"); cjLabel.setText("演员C(忠臣2)"); cExplainRadioButton.setText("辩解"); cKillRadioButton.setText("遵从命令"); djLabel.setText("演员D(奸臣)"); directjTextArea.setEditable(false); directjTextArea.setColumns(20); directjTextArea.setRows(5); directjTextArea.setText("由我安排"); directjTextArea.setAutoscrolls(false); directjTextArea.setName(""); // NOI18N jScrollPane1.setViewportView(directjTextArea); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(39, 39, 39) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(actionButton) .addComponent(jLabel1))) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(djLabel) .addComponent(dTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 146, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(0, 12, Short.MAX_VALUE) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 185, javax.swing.GroupLayout.PREFERRED_SIZE))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(cKillRadioButton) .addComponent(cExplainRadioButton) .addComponent(cjLabel) .addComponent(aTextField) .addComponent(ajLabel) .addComponent(bTextField) .addComponent(bjLabel) .addComponent(cTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 173, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(30, 30, 30)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(layout.createSequentialGroup() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(actionButton) .addGap(10, 10, 10) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() .addComponent(ajLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(aTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(bjLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(bTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(cjLabel) .addGap(2, 2, 2) .addComponent(cTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cExplainRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cKillRadioButton) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(djLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(dTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(78, 78, 78)))) ); pack(); }// </editor-fold> private void actionButtonMouseClicked(java.awt.event.MouseEvent evt) { SignalSlotHandle.emit(this, "action", new Object[]{}); } /** * @param args the command line arguments */ public static void main(String args[]) { /* Set the Nimbus look and feel */ //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) "> /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html */ try { for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { javax.swing.UIManager.setLookAndFeel(info.getClassName()); break; } } } catch (ClassNotFoundException ex) { java.util.logging.Logger.getLogger(TestJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (InstantiationException ex) { java.util.logging.Logger.getLogger(TestJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { java.util.logging.Logger.getLogger(TestJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (javax.swing.UnsupportedLookAndFeelException ex) { java.util.logging.Logger.getLogger(TestJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } //</editor-fold> /* Create and display the form */ java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new TestJFrame().setVisible(true); } }); } // Variables declaration - do not modify private javax.swing.JTextField aTextField; private javax.swing.JButton actionButton; private javax.swing.JLabel ajLabel; private javax.swing.JTextField bTextField; private javax.swing.JLabel bjLabel; private javax.swing.ButtonGroup buttonGroup1; private javax.swing.JRadioButton cExplainRadioButton; private javax.swing.JRadioButton cKillRadioButton; private javax.swing.JTextField cTextField; private javax.swing.JLabel cjLabel; private javax.swing.JTextField dTextField; private javax.swing.JTextArea directjTextArea; private javax.swing.JLabel djLabel; private javax.swing.JLabel jLabel1; private javax.swing.JScrollPane jScrollPane1; // End of variables declaration }
只需要关注这个类上部分,此类界面由Netbeans自动生成。使用信号/槽机制,首先是注册信号/槽对,在构造函数完成。
SignalSlotHandle.connect(this, "action", this, "aAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "bAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "cAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "dAct", new Class[]{}); // 导演说开始 SignalSlotHandle.connect(this, "action", this, "directAct", new Class[]{}); SignalSlotHandle.connect(this, "bdShutup", this, "bInRole", String.class); // 皇帝演员让忠臣1闭嘴, 将信号通过参数String传递,以便槽函数根据信号作出处理 SignalSlotHandle.connect(this, "bdShutup", this, "dInRole", String.class); // 皇帝演员让奸臣1闭嘴,String参数同上 SignalSlotHandle.connect(this, "bdShutup", this, "cInRole", String.class, Boolean.class); // 皇帝发火了,忠臣2忐忑中,String参数同上,boolean用于设置忐忑状态, ·始终是true(表示进入忐忑状态) SignalSlotHandle.connect(this.bjLabel, "sayed", this, "cInRole", String.class, Boolean.class); // 忠臣1演员劝说完, 忠臣2演员选择辨解,boolean类型变量用于标识忠臣1(true)和奸臣1(false),String参数同上 SignalSlotHandle.connect(this.djLabel, "sayed", this, "cInRole", String.class, Boolean.class); // 奸臣1演员火上浇油完,忠臣2选择遵从命令(自杀),String参数同上 SignalSlotHandle.connect(this, "bdsayed", this, "aInRole", String.class); // 忠臣1和奸臣1说完话后,皇帝大怒,发送“bdShutup"信号,String参数同上 SignalSlotHandle.connect(this, "newcommond", this, "bInRole", String.class); // 皇帝发布新的命令,不杀忠臣2 SignalSlotHandle.connect(this, "newcommond", this, "cInRole", String.class, Boolean.class); // boolean类型始终为false SignalSlotHandle.connect(this, "newcommond", this, "dInRole", String.class);
当点击Action按钮时,当前类发出"action"信号,调用相应槽函数aAct、bAct、cAct、dAct。首先皇帝角色发出忠臣2自刎的命令(aAct),其他角色分别等待设定时间。之后忠臣2角色表现无助(cAct);忠臣1角色进行劝说(bAct),发出"sayed"信号,忠臣2角色接收后决定进行辩解(cInRole);奸臣角色进行火上浇油的劝说(dAct),发出"sayed"信号,忠臣2角色接收后决定遵从命令自杀(cInRole)。在bAct、dAct槽函数有个计数,用于当忠臣1和奸臣都在说时,发出"bdsayed"信号给皇帝,皇帝角色命令他们闭嘴(发出信号"bdShutup"),忠臣1和奸臣收到信号后保持沉默(bInRole和dInRole),忠臣2收到信号后表现出忐忑不安(cInRole)。皇帝角色在大家沉默10秒后,发布新命令(信号"newcommond")赦免忠臣2。忠臣1、忠臣2、奸臣分别表现不同的反应(bInRole、cInRole、dInRole)。
在程序中有个boolean类型变量worried,用于控制忠臣2角色是否表现出忐忑不安,当worried为true时,界面上“辩解”和“遵从命令”单选按钮不停交替选中,以表现忐忑不安的状态。设置这个变量是因为每次函数调用都是在独立的线程中执行,第二次调用并不影响第一次调用,所以使用一个“全局”量控制忐忑不安状态时调用函数的结束。
问题:
1、 由于信号/槽处理类为静态类,从包加载起对象就已经存在,直到JVM销毁。所以同步列表需要实时维护,当注册的对象不存在时应该及时的清理列表,以减少资源占用。
2、 在emit函数中使用Thread类进行线程创建,存在与Swing图形组件的兼容性问题。
3、 例子程序在反复点击Action按钮时,会抛出InvocationTargetException异常和ClassCastException异常(java.lang.Character cannot be cast to javax.swing.Painter)。异常的发生通常在cInRole函数中的这个循环中。当减小此循环的持续时间时,异常出现的概率明显减小。
while (this.worried) { this.cExplainRadioButton.doClick(); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException ex) { Logger.getLogger(TestJFrame.class.getName()).log(Level.SEVERE, null, ex); } this.cKillRadioButton.doClick(); }
相关文章推荐
- 为JAVA添加信号/槽支持(二)
- 用java实现给图片增加图片水印或者文字水印(也支持视频图像帧添加水印)
- eclipse4.3添加java8的支持
- java-cef系列视频第三集:添加flash支持
- Java添加UTF-7字符集支持
- Cocos项目为安卓添加获取手机信号强度和网络类型的功能(JAVA/C++/Lua)
- Android4.0-4.4 添加实体按键振动支持的方法(java + smali版本)
- java PDF添加图层的方法 支持多页图层添加
- 用java实现给图片增加图片水印或者文字水印(也支持视频图像帧添加水印)
- 一个自己用的代码备份工具,支持delphi,android,java,可以自己添加配置,灵活支持大部分编程语言
- ubuntu (16.04) server 英文原版 添加中文语言支持 消除java 程序、mysql 数据库不能处理中文的错误
- 为SSH架构的java web项目添加flex支持
- eclipse添加tomcat支持 (java基础)
- 为firefox手动添加java支持
- 用java实现给图片增加图片水印或者文字水印(也支持视频图像帧添加水印)
- 用java实现给图片增加图片水印或者文字水印(也支持视频图像帧添加水印)
- 添加javabrowser 支持中文