• Java实现Qt的SIGNAL-SLOT机制


    SIGNAL-SLOT是Qt的一大特色,使用起来十分方便。在传统的AWT和Swing编程中,我们都是为要在

    监听的对象上添加Listener监听器。被监听对象中保存有Listener的列表,当相关事件发生时,被监听
    对象会通知所有Listener。而在Qt中,我们只需通过connect方法连接两个对象上的方法就可以了,非常
    方便、优雅地实现了传统的观察者Observer模式。
     
    Qt是如何办到的呢?对于发出SIGNAL的对象,我们需要在其头文件定义中声明Q_Object宏,之后Qt的
    预处理器MOC会为我们自动添加上相应的代码来实现SIGNAL-SLOT机制。这与AspectJ自定义了Javac
    编译器很类似,都是通过增强编译器来自动添加相应的代码。
     
    增强编译或增加预处理太复杂,怎样能够简单的实现这种机制呢?首先我们实现一个类似的QObject类,
    需要发射SIGNAL的类都要继承它。在QObject类中,我们自动为其子类提供监听器列表,查找SLOT方法,
    信号发射等功能。
     
    QObject.java
     
    1.在连接方法中,我们将信号和新建的ReceiverSlot类保存到Map中,从而将它们关联起来。
    [java] view plaincopy
     
    1. public static void connect(QObject sender, String signal, Object receiver, String slot) {  
    2.     if (sender.signalSlotMap == null)  
    3.         sender.signalSlotMap = new HashMap<String, List<ReceiverSlot>>();  
    4.       
    5.     List<ReceiverSlot> slotList = sender.signalSlotMap.get(signal);  
    6.     if (slotList == null) {  
    7.         slotList = new LinkedList<ReceiverSlot>();  
    8.         sender.signalSlotMap.put(signal, slotList);  
    9.     }  
    10.     slotList.add(createReceiverSlot(receiver, slot));  
    11. }  
    [java] view plaincopy
     
    1. static class ReceiverSlot {  
    2.     Object receiver;  
    3.     Method slot;  
    4.     Object[] args;  
    5. }  
     
    2.在创建ReceiverSlot时,我们解析SLOT方法名,如将slot(String,String)解析为方法slot,参数两个String。
    如果解析失败我们就认为该SLOT仍是一个信号,也就是SIGNAL-SIGNAL的连接。这种情况下,我们需要
    传递调用的不是receiver的SLOT方法,而是emit方法继续发射信号。
    [java] view plaincopy
     
    1. private static ReceiverSlot createReceiverSlot(Object receiver, String slot) {  
    2.     ReceiverSlot receiverSlot = new ReceiverSlot();  
    3.     receiverSlot.receiver = receiver;  
    4.       
    5.     Pattern pattern = Pattern.compile("(\w+)\(([\w+,]*)\)");  
    6.     Matcher matcher = pattern.matcher(slot);  
    7.     if (matcher.matches() && matcher.groupCount() == 2) {  
    8.         // 1.Connect SIGNAL to SLOT  
    9.         try {  
    10.             String methodName = matcher.group(1);  
    11.             String argStr = matcher.group(2);  
    12.             ArrayList<String> argList = new ArrayList<String>();  
    13.               
    14.             pattern = Pattern.compile("\w+");  
    15.             matcher = pattern.matcher(argStr);  
    16.             while (matcher.find())  
    17.                 argList.add(matcher.group());  
    18.             String[] arguments = argList.toArray(new String[0]);  
    19.               
    20.             receiverSlot.slot = findMethod(receiver, methodName, arguments);  
    21.             receiverSlot.args = new Object[0];  
    22.         }  
    23.         catch (Exception e) {  
    24.             e.printStackTrace();  
    25.         }  
    26.     }   
    27.     else {  
    28.         // 2.Connect SIGNAL to SIGNAL  
    29.         if (receiver instanceof QObject) {  
    30.             receiverSlot.slot = emitMethod;  
    31.             receiverSlot.args = new Object[] { slot };  
    32.         }  
    33.     }  
    34.       
    35.     return receiverSlot;  
    36. }  
    [java] view plaincopy
     
    1. private static Method emitMethod;  
    2.   
    3. protected Map<String, List<ReceiverSlot>> signalSlotMap;  
    4.   
    5. static {  
    6.     try {  
    7.         emitMethod = QObject.class.getDeclaredMethod("emit", String.class, Object[].class);  
    8.     } catch (Exception e) {  
    9.         e.printStackTrace();  
    10.     }  
    11. }  
     
    3.解析后,如果是SIGNAL-SLOT的连接,那我我们根据方法名和参数找到该方法,准备反射调用。
    [java] view plaincopy
     
    1. private static Method findMethod(Object receiver, String methodName, String[] arguments)  
    2.         throws NoSuchMethodException {  
    3.       
    4.     Method slotMethod = null;  
    5.       
    6.     if (arguments.length == 0)   
    7.         slotMethod = receiver.getClass().getMethod(methodName, new Class[0]);  
    8.     else {  
    9.         for (Method method : receiver.getClass().getMethods()) {  
    10.               
    11.             // 1.Check method name  
    12.             if (!method.getName().equals(methodName))  
    13.                 continue;  
    14.               
    15.             // 2.Check parameter number  
    16.             Class<?>[] paramTypes = method.getParameterTypes();  
    17.             if (paramTypes.length != arguments.length)  
    18.                 continue;  
    19.               
    20.             // 3.Check parameter type  
    21.             boolean isMatch = true;  
    22.             for (int i = 0; i < paramTypes.length; i++) {  
    23.                 if (!paramTypes[i].getSimpleName().equals(arguments[i])) {  
    24.                     isMatch = false;  
    25.                     break;  
    26.                 }  
    27.             }  
    28.             if (isMatch) {  
    29.                 slotMethod = method;  
    30.                 break;  
    31.             }  
    32.         }  
    33.           
    34.         if (slotMethod == null)  
    35.             throw new NoSuchMethodException("Cannot find method[" + methodName +   
    36.                     "] with parameters: " + Arrays.toString(arguments));  
    37.     }  
    38.       
    39.     return slotMethod;  
    40. }  
     
    4.发射信号时,我们取到所有与该SIGNAL关联的ReceiverSlot类,逐个发射信号。
    [java] view plaincopy
     
    1. protected void emit(String signal, Object... args) {  
    2.     System.out.println(getClass().getSimpleName() + " emit signal " + signal);  
    3.       
    4.     if (signalSlotMap == null)  
    5.         return;  
    6.       
    7.     List<ReceiverSlot> slotList = signalSlotMap.get(signal);        
    8.     if (slotList == null || slotList.isEmpty())  
    9.         return;  
    10.       
    11.     for (ReceiverSlot objSlot : slotList) {  
    12.         try {  
    13.             if (objSlot.slot == emitMethod)  
    14.                 objSlot.slot.invoke(objSlot.receiver, objSlot.args[0], args);  
    15.             else  
    16.                 objSlot.slot.invoke(objSlot.receiver, args);  
    17.         }  
    18.         catch (Exception e) {  
    19.             e.printStackTrace();  
    20.         }  
    21.     }  
    22. }  
     
    之后,我们实现一个它的子类QWidget,将常用的Swing控件都封装在QWidget的子类中,为这些控件提供
    常见的预定义的SIGNAL,像Qt中的clicked和returnPressed。
     
    QWidget.java
    [java] view plaincopy
     
    1. public class QWidget<T extends JComponent> extends QObject implements QSwing<T> {  
    2.   
    3.     protected T widget;  
    4.       
    5.     public QWidget(Class<T> clazz) {  
    6.         try {  
    7.             widget = clazz.newInstance();  
    8.         }   
    9.         catch (Exception e) {  
    10.             e.printStackTrace();  
    11.         }  
    12.     }  
    13.       
    14.     @Override  
    15.     public T getSwingWidget() {  
    16.         return this.widget;  
    17.     }  
    18.   
    19. }  
     
    以下是封装了JButton和JTextField的QWidget子类。
     
    QPushButton.java
    [java] view plaincopy
     
    1. public class QPushButton extends QWidget<JButton> {  
    2.   
    3.     public static final String CLICKED = "clicked";  
    4.       
    5.     public QPushButton(String text) {  
    6.         super(JButton.class);  
    7.           
    8.         widget.setText(text);  
    9.         widget.addActionListener(new ActionListener() {  
    10.             @Override  
    11.             public void actionPerformed(ActionEvent e) {  
    12.                 emit(CLICKED);  
    13.             }  
    14.         });  
    15.     }  
    16.   
    17. }  
     
    QLineEdit.java
    [java] view plaincopy
     
    1. public class QLineEdit extends QWidget<JTextField> {  
    2.   
    3.     public static final String RETURN_PRESSED = "returnPressed";  
    4.       
    5.     public QLineEdit() {  
    6.         super(JTextField.class);  
    7.           
    8.         widget.addActionListener(new ActionListener() {  
    9.             @Override  
    10.             public void actionPerformed(ActionEvent e) {  
    11.                 emit(RETURN_PRESSED);  
    12.             }  
    13.         });  
    14.     }  
    15.   
    16. }  
     
    下面我们来写个测试类实验下Java版的SIGNAL-SLOT机制,依旧是之前的浏览器的例子。
     
    AddressBar.java
    [java] view plaincopy
     
    1. public class AddressBar extends QWidget<JPanel> {  
    2.       
    3.     /** 
    4.      * SIGNAL 
    5.      */  
    6.     public static final String NEW_BUTTON_CLICKED = "newButtonClicked";  
    7.     public static final String GO_TO_ADDRESS = "goToAddress(String,String)";  
    8.       
    9.     /** 
    10.      * SLOT 
    11.      */  
    12.     public static final String HANDLE_GO_TO_ADDRESS = "handleGoToAddress()";  
    13.       
    14.       
    15.     private QPushButton newButton;  
    16.     private QLineEdit addressEdit;  
    17.     private QPushButton goButton;  
    18.       
    19.       
    20.     public AddressBar() {  
    21.         super(JPanel.class);  
    22.           
    23.         // 1.Create widget  
    24.         newButton = new QPushButton("New");  
    25.         addressEdit = new QLineEdit();  
    26.         goButton = new QPushButton("Go");  
    27.           
    28.         // 2.Set property  
    29.         addressEdit.getSwingWidget().setColumns(10);  
    30.           
    31.         // 3.Connect signal-slot  
    32.         connect(newButton, QPushButton.CLICKED, this, NEW_BUTTON_CLICKED);  
    33.         connect(addressEdit, QLineEdit.RETURN_PRESSED, this, HANDLE_GO_TO_ADDRESS);  
    34.         connect(goButton, QPushButton.CLICKED, this, HANDLE_GO_TO_ADDRESS);  
    35.           
    36.         // 4.Add to layout  
    37.         getSwingWidget().add(newButton.getSwingWidget());  
    38.         getSwingWidget().add(addressEdit.getSwingWidget());  
    39.         getSwingWidget().add(goButton.getSwingWidget());  
    40.     }  
    41.       
    42.     public void handleGoToAddress() {  
    43.         emit(GO_TO_ADDRESS, addressEdit.getSwingWidget().getText(), "test string");  
    44.     }  
    45.       
    46. }  
     
    TabBar.java
    [java] view plaincopy
     
    1. public class TabBar extends JTabbedPane {  
    2.       
    3.     /** 
    4.      * SLOT 
    5.      */  
    6.     public static final String HANDLE_NEW_TAB = "handleNewTab()";  
    7.     public static final String HANDLE_GO_TO_SITE = "goToSite(String,String)";  
    8.       
    9.       
    10.     public TabBar() {  
    11.         handleNewTab();  
    12.     }  
    13.       
    14.     public void handleNewTab() {  
    15.         WebView tab = new WebView();  
    16.         add("blank", tab);  
    17.     }  
    18.       
    19.     public void goToSite(String url, String testStr) {  
    20.         System.out.println("Receive url: " + url + ", " + testStr);  
    21.           
    22.         WebView tab = (WebView) getSelectedComponent();  
    23.         tab.load(url);  
    24.     }  
    25.       
    26. }  
     
    MainWindow.java
    [java] view plaincopy
     
    1. public class MainWindow extends JFrame {  
    2.   
    3.     public static void main(String[] args) {  
    4.         JFrame window = new MainWindow();  
    5.         window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
    6.         window.setSize(320, 340);  
    7.         window.setVisible(true);  
    8.     }  
    9.       
    10.     public MainWindow() {  
    11.         // 1.Create widget  
    12.         AddressBar addressBar = new AddressBar();  
    13.         TabBar tabBar = new TabBar();  
    14.           
    15.         // 2.Set property  
    16.           
    17.         // 3.Connect signal-slot  
    18.         QObject.connect(addressBar, AddressBar.NEW_BUTTON_CLICKED, tabBar, TabBar.HANDLE_NEW_TAB);  
    19.         QObject.connect(addressBar, AddressBar.GO_TO_ADDRESS, tabBar, TabBar.HANDLE_GO_TO_SITE);  
    20.           
    21.         // 4.Add to layout  
    22.         GridBagLayout layout = new GridBagLayout();  
    23.         setLayout(layout);  
    24.         GridBagConstraints grid = new GridBagConstraints();  
    25.         grid.fill = GridBagConstraints.BOTH;  
    26.         grid.gridx = grid.gridy = 0;  
    27.         grid.weightx = 1.0;  
    28.         grid.weighty = 0.1;  
    29.         add(addressBar.getSwingWidget(), grid);  
    30.         grid.fill = GridBagConstraints.BOTH;  
    31.         grid.gridx = 0;  
    32.         grid.gridy = 1;  
    33.         grid.weightx = 1.0;  
    34.         grid.weighty = 0.9;  
    35.         add(tabBar, grid);  
    36.     }  
    37.       
    38. }  
    39.   
    40.   
    41. @SuppressWarnings("serial")  
    42. class WebView extends JEditorPane {  
    43.       
    44.     public WebView() {  
    45.         setEditable(false);  
    46.     }  
    47.       
    48.     public void load(final String url) {  
    49.         SwingUtilities.invokeLater(new Runnable() {  
    50.             @Override  
    51.             public void run() {  
    52.                 try {  
    53.                     WebView.this.setPage(url);  
    54.                 } catch (IOException e) {  
    55.                     e.printStackTrace();  
    56.                 }  
    57.             }  
    58.         });  
    59.     }  
    60.       
    61. }  
     
    测试一下吧,运行起来的效果就是这样。
     
    新建Tab页和前往该地址事件都可以成功地从AddressBar传递到TabBar。怎么样,这种Java版的
    SIGNAL-SLOT是不是很方便。多开拓自己的视野,借鉴优秀的思想,我们才能做出更好的设计!
    希望你喜欢本文。
     
    参考:http://blog.csdn.net/dc_726/article/details/7632430
  • 相关阅读:
    tomcat中配置jmx监控
    常用sql
    String、StringBuffer、StringBuilder的不同使用场景
    求交集的几种方法
    使用liunx部署的心得
    几种有助于开发的注释方式。
    SpringDataJPA的几个使用记录
    今年要完成的几件事
    研究kisso跨域登录的心得
    SpringBoot使用的心得记录
  • 原文地址:https://www.cnblogs.com/findumars/p/4755331.html
Copyright © 2020-2023  润新知